he 6 hónapja
commit
e7b6cc843e
100 módosított fájl, 32770 hozzáadás és 0 törlés
  1. 5 0
      .gitignore
  2. 8 0
      .idea/.gitignore
  3. 36 0
      App.vue
  4. 21 0
      LICENSE
  5. 15 0
      README.md
  6. 8 0
      changelog.md
  7. 45 0
      common/config.js
  8. 1695 0
      common/global.css
  9. 1458 0
      common/uni.css
  10. 773 0
      components/MediaUploader.vue
  11. 50 0
      components/custom-navbar/custom-navbar.vue
  12. 20 0
      components/progress.vue
  13. 27 0
      components/statusBar.vue
  14. 233 0
      components/u-city-select.vue
  15. 429 0
      config/api.js
  16. 57 0
      config/mixins/index.js
  17. 183 0
      config/request.js
  18. 24 0
      config/request/index.js
  19. 214 0
      config/request/interceptors.js
  20. 20 0
      index.html
  21. 44 0
      js_sdk/js-base64/.attic/test-moment/dankogai.js
  22. 24 0
      js_sdk/js-base64/.attic/test-moment/es5.js
  23. 25 0
      js_sdk/js-base64/.attic/test-moment/es6.js
  24. 40 0
      js_sdk/js-base64/.attic/test-moment/index.html
  25. 25 0
      js_sdk/js-base64/.attic/test-moment/large.js
  26. 4535 0
      js_sdk/js-base64/.attic/test-moment/moment.js
  27. 19 0
      js_sdk/js-base64/.attic/test-moment/yoshinoya.js
  28. BIN
      js_sdk/js-base64/1x1.png
  29. 27 0
      js_sdk/js-base64/LICENSE.md
  30. 94 0
      js_sdk/js-base64/README.md
  31. 47 0
      js_sdk/js-base64/base64.html
  32. 231 0
      js_sdk/js-base64/base64.js
  33. 0 0
      js_sdk/js-base64/base64.min.js
  34. 18 0
      js_sdk/js-base64/bower.json
  35. 9 0
      js_sdk/js-base64/package.js
  36. 59 0
      js_sdk/js-base64/package.json
  37. 44 0
      js_sdk/js-base64/test/dankogai.js
  38. 24 0
      js_sdk/js-base64/test/es5.js
  39. 25 0
      js_sdk/js-base64/test/es6.js
  40. 39 0
      js_sdk/js-base64/test/index.html
  41. 25 0
      js_sdk/js-base64/test/large.js
  42. 19 0
      js_sdk/js-base64/test/yoshinoya.js
  43. 29 0
      locale/de.json
  44. 29 0
      locale/en.json
  45. 25 0
      locale/es.json
  46. 25 0
      locale/fr.json
  47. 19 0
      locale/index.js
  48. 28 0
      locale/ja.json
  49. 29 0
      locale/sa.json
  50. 36 0
      locale/uni-app.ja.json
  51. 29 0
      locale/zh-Hans.json
  52. 28 0
      locale/zh-Hant.json
  53. 73 0
      main.js
  54. 121 0
      manifest.json
  55. 2181 0
      package-lock.json
  56. 91 0
      package.json
  57. 262 0
      packageOrder/pages/after-sale/index.vue
  58. 877 0
      packageOrder/pages/detail/index.vue
  59. 474 0
      packageOrder/pages/list/index.vue
  60. 692 0
      packageOrder/pages/list/record.vue
  61. 377 0
      packageOrder/pages/logistics/index.vue
  62. 396 0
      packageOrder/pages/payment/index.vue
  63. 180 0
      packageOrder/pages/score/index.vue
  64. 162 0
      packageOrder/pages/score/product-score.vue
  65. 166 0
      packageShop/pages/address-edit/index.vue
  66. 178 0
      packageShop/pages/address/index.vue
  67. 78 0
      packageShop/pages/article/detail.vue
  68. 684 0
      packageShop/pages/cart/index.vue
  69. 945 0
      packageShop/pages/detail/index.vue
  70. 224 0
      packageShop/pages/score/index.vue
  71. 166 0
      packageShop/pages/search/index.vue
  72. 917 0
      packageShop/pages/search/product-list.vue
  73. 487 0
      packageShop/pages/settle/index.vue
  74. 165 0
      packageUser/pages/about/index.vue
  75. 205 0
      packageUser/pages/address/edit.vue
  76. 195 0
      packageUser/pages/address/list.vue
  77. 867 0
      packageUser/pages/collect/index.vue
  78. 223 0
      packageUser/pages/feedback/index.vue
  79. 106 0
      packageUser/pages/language/index.vue
  80. 570 0
      packageUser/pages/level/index.vue
  81. 923 0
      packageUser/pages/login/index.vue
  82. 260 0
      packageUser/pages/register/index.vue
  83. 93 0
      packageUser/pages/richtext/index.vue
  84. 1044 0
      packageUser/pages/share/index.vue
  85. 1118 0
      packageUser/pages/team/index.vue
  86. 98 0
      packageUser/pages/user-info/account-change.vue
  87. 186 0
      packageUser/pages/user-info/birthday-change.vue
  88. 323 0
      packageUser/pages/user-info/index.vue
  89. 118 0
      packageUser/pages/user-info/sex-change.vue
  90. 257 0
      pages.json
  91. 2454 0
      pages/index/indexNew.vue
  92. 576 0
      pages/order/after-progress.vue
  93. 315 0
      pages/order/after.vue
  94. 439 0
      pages/order/detail.vue
  95. 255 0
      pages/order/history-after.vue
  96. 427 0
      pages/order/list.vue
  97. 109 0
      pages/order/logistics.vue
  98. 359 0
      pages/order/payment.vue
  99. 217 0
      pages/order/score.vue
  100. 166 0
      pages/order/search.vue

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/node_modules/
+/unpackage/
+
+/unpackage/
+/unpackage/dist/

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 36 - 0
App.vue

@@ -0,0 +1,36 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	.status_bar {
+		height: var(--status-bar-height);
+		width: 100%;
+	}
+
+	.top_view {
+		height: var(--status-bar-height);
+		width: 100%;
+		position: fixed;
+		top: 0;
+		z-index: 999;
+	}
+
+	/*每个页面公共css */
+
+
+	@import "@/uni_modules/uview-plus/index.scss";
+
+	/*每个页面公共css */
+	@import "common/global.css";
+</style>

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 DCloud
+
+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.

+ 15 - 0
README.md

@@ -0,0 +1,15 @@
+# hello-i18n
+
+# en
+
+A demo project for uni-app globalization
+
+This template include uni-framework, manifest.json, pages.json, tabbar, Page, Component, API
+
+
+# zh-hans
+
+uni-app 国际化演示
+
+包含 uni-framework、manifest.json、pages.json、tabbar、页面、组件、API
+

+ 8 - 0
changelog.md

@@ -0,0 +1,8 @@
+## 1.0.2(2022-06-30)
+- 新增 支持 ios 安全区
+## 1.0.1(2021-11-26)
+- 新增 schema 国际化 (HBuilderX 3.3+)
+- 修复 非 Android 平台切换语音无法实时变化的问题
+- 修复 设置某些语言下无法生效的问题 (HBuilderX 3.3+)
+## 1.0.0(2021-10-20)
+- 初始化

+ 45 - 0
common/config.js

@@ -0,0 +1,45 @@
+/**
+ *  全局配置文件
+ */
+
+const config = {
+	DEVE_URL: "http://localhost:8085",
+	// PRODUCT_URL: "http://localhost:8085",
+	PRODUCT_URL: "https://api.ndtk.site/api",
+	IMG_URL: 'https://abinmalm.oss-cn-shenzhen.aliyuncs.com/icon/',
+	UPLOAD_URL: 'https://api.ndtk.site/api/blade-resource/oss/endpoint/put-file',
+	AMAP_KEY: '',
+	QQMAP_KEY: '',
+	HAS_LIVE: false,
+	MAIN_COLOR: '#3C86F7',
+	initShop: {
+		name: 'MT5',
+		copyright: '',
+		domain: 'http://192.168.0.118:8088'
+	},
+	shareImg: "https://ndtk.tos-cn-guangzhou.volces.com/uploads/156adea827104e38ae0e25ec4701ecfe.jpg",
+	initShare: {
+		user_poster_bg: 'http://abinmalm.oss-cn-shenzhen.aliyuncs.com/upload/20220423/66daf320d638427560b55d63b3b86277.jpg',
+		goods_poster_bg: 'http://abinmalm.oss-cn-shenzhen.aliyuncs.com/upload/20220423/ad95ddb2541e775d0f356800b3458120.jpg',
+		groupon_poster_bg: '',
+		title: '邀请有礼',
+		image: 'https://gd2.alicdn.com/imgextra/i3/2617841713/O1CN01wh9SmI1OWb4ta4BEH_!!2617841713.jpg'
+	}
+}
+
+// 同时提供命名导出和默认导出
+export const {
+	DEVE_URL,
+	PRODUCT_URL,
+	IMG_URL,
+	UPLOAD_URL,
+	AMAP_KEY,
+	QQMAP_KEY,
+	HAS_LIVE,
+	MAIN_COLOR,
+	initShop,
+	initShare,
+	shareImg
+} = config
+
+export default config

+ 1695 - 0
common/global.css

@@ -0,0 +1,1695 @@
+@font-face {
+	font-family: 'uicon-iconfont';
+	src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
+}
+
+html {
+	width: 100%;
+	-webkit-text-size-adjust: 100%;
+	-ms-text-size-adjust: 100%;
+}
+
+page {
+	outline: 0;
+	-webkit-text-size-adjust: none;
+	-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+}
+
+body,
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+code,
+form,
+fieldset,
+legend,
+input,
+textarea,
+p,
+blockquote,
+th,
+td,
+hr,
+button,
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+	margin: 0;
+	padding: 0;
+}
+
+page,
+view,
+text,
+navigator,
+button,
+image {
+	box-sizing: border-box;
+}
+
+body {
+	font-size: 28upx;
+	background: #F6F6F6;
+	color: #000000;
+	overflow-x: hidden;
+}
+
+input,
+select,
+textarea {
+	font-size: 100%;
+	-webkit-appearance: none;
+	background: transparent;
+}
+
+textarea {
+	border: none;
+	outline: none;
+}
+
+button {
+	border: 0;
+}
+
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+
+input {
+	border: 0;
+	outline: 0;
+	/* color: #000000; */
+}
+
+text {
+	font-size: 24rpx;
+}
+
+uni-view {
+	box-sizing: border-box;
+	font-size: 24rpx;
+	/* line-height: 1.2; */
+}
+
+.dark {
+	min-height: 100vh;
+	background: #102222 !important;
+	color: #FFFFFF;
+	/* overflow-x: hidden; */
+	/* color字体颜色 */
+	/* 背景色 */
+	/* 边框颜色 */
+	/* margin外边距 */
+	/* padding内边距 */
+	/* 宽度百分比 */
+	/* 宽度upx */
+	/* 高度 */
+	/* 行高 */
+	/* 边框圆角 */
+	/* 透明度 */
+	/* 三角形 */
+	/* 字体渐变 */
+	/* 去除chrome input默认背景 */
+	/*清除ie的默认选择框样式清除,隐藏下拉箭头*/
+	/* 不换行超出隐藏 */
+	/* 其它 */
+	/* 下划线 */
+}
+
+/* window浏览器顶级容器固定宽度375px */
+.container-win {
+	width: 375px;
+	margin: 0 auto;
+	background: #F7F7F7;
+}
+
+.container {
+	width: 750rpx;
+	background: #F7F7F7;
+	/* height: 100vh;
+	max-height: 100vh; */
+}
+
+.flex-center {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: center;
+}
+
+.flex-col-center {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+
+}
+
+.shadow {
+	box-shadow: 0px 2rpx 28rpx 2rpx rgba(0, 0, 0, 0.08);
+}
+
+.width100 {
+	width: 100%;
+}
+
+.width50 {
+	width: 50%;
+}
+
+.width40 {
+	width: 40%;
+}
+
+.width60 {
+	width: 60%;
+}
+
+.width70 {
+	width: 70%;
+}
+
+.width30 {
+	width: 30%;
+}
+
+.width80 {
+	width: 80%;
+}
+
+.width700 {
+	width: 700rpx;
+}
+
+.width20 {
+	width: 20%;
+}
+
+.height0 {
+	height: 0;
+}
+
+.height-vh {
+	min-height: 100vh;
+}
+
+.height100 {
+	height: 100%;
+}
+
+.height70 {
+	height: 70%;
+}
+
+
+
+.height64 {
+	height: 64rpx;
+}
+
+.height72 {
+	height: 72rpx;
+}
+
+.height96 {
+	height: 96rpx;
+}
+
+.height104 {
+	height: 104rpx;
+}
+
+.fixed {
+	position: fixed;
+}
+
+.top-0 {
+	top: 0;
+}
+
+.bottom-0 {
+	bottom: 0;
+}
+
+.left-0 {
+	left: 0;
+}
+
+.right-0 {
+	right: 0;
+}
+
+.width-auto {
+	width: auto;
+}
+
+.height-auto {
+	height: auto;
+}
+
+.line-through {
+	text-decoration: line-through;
+}
+
+.radius-8 {
+	border-radius: 8rpx;
+}
+
+.radius-16 {
+	border-radius: 16rpx;
+}
+
+.radius-24 {
+	border-radius: 24rpx;
+}
+
+.radius-b-16 {
+	border-radius: 0 0 16rpx 16rpx;
+}
+
+/* ios设置安全距离高度,非iOS默认为0 */
+.safearea {
+	padding-bottom: 0;
+	padding-bottom: constant(safe-area-inset-bottom);
+	padding-bottom: env(safe-area-inset-bottom);
+	box-sizing: content-box !important;
+}
+
+.bg-999 {
+	background: #999999;
+}
+
+.bg-zero {
+	background: #cecece;
+	opacity: 0.8;
+	border: #585858 1rpx solid;
+	color: #3e3e3e;
+}
+
+.bg-black {
+	background: #292C35;
+}
+
+.bg-F7F7F7 {
+	background: #F7F7F7;
+}
+
+.bg-white {
+	background: white;
+}
+
+.bg-F9 {
+	background: #F9F9F9;
+}
+
+.bg-000 {
+	background: #000;
+}
+
+.bg-red {
+	background: #FD5D5F;
+}
+
+.bg-red-sign {
+	background: #FD5D5F;
+}
+
+.bg-CCC-sign {
+	background: #CCCCCC;
+}
+
+.bg-CCC {
+	background: #CCCCCC;
+}
+
+.bg-orangered {
+	background: orangered;
+}
+
+.bg-blue {
+	background: #4894FF;
+}
+
+.bg-main {
+	background: #ffffff;
+	min-height: 100vh;
+}
+
+.bg-main2 {
+	background: #FFFFFF;
+}
+
+.bg-EEE {
+	background: #eeeeee;
+}
+
+.bg-transparent {
+	background: transparent;
+}
+
+.dark .bg-main {
+	background: #102030;
+}
+
+.dark .bg-main2 {
+	background: #102030;
+}
+
+.dark.bg-main {
+	background: #102030;
+}
+
+.dark.bg-main-select {
+	background: #c5f516;
+}
+
+.bg-mediumpurple {
+	background: #ddd9ff;
+}
+
+.productImg {
+	border-radius: 20rpx;
+	width: 220rpx;
+	height: 220rpx;
+}
+
+.radius20 {
+	border-radius: 20rpx;
+}
+
+.region {
+	/* 阴影效果 */
+	box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	border-radius: 20rpx;
+	width: 730rpx;
+	height: auto;
+	padding-bottom: 30rpx;
+	padding-top: 30rpx;
+	background-color: white;
+	margin-bottom: 10rpx;
+	margin-left: 10rpx;
+}
+
+.region2 {
+	/* 阴影效果 */
+	box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	border-radius: 20rpx;
+	width: 730rpx;
+	height: auto;
+	padding-bottom: 10rpx;
+	padding-top: 10rpx;
+	background-color: white;
+	margin-bottom: 10rpx;
+	margin-left: 10rpx;
+}
+
+.region1 {
+	/* 阴影效果 */
+	box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	border-radius: 20rpx;
+	width: 730rpx;
+	height: auto;
+	padding-top: 30rpx;
+	background-color: white;
+	margin-bottom: 10rpx;
+	margin-left: 10rpx;
+}
+
+.bg-orchid {
+	background: #9968c3;
+}
+
+.bg-darkblue {
+	background: #3F3BAF;
+}
+
+.bg-green {
+	background: #53D084;
+}
+
+.headerbg {
+	background: linear-gradient(315deg, #53509F 0%, #3A386F 51%, #3B3971 100%);
+}
+
+.btn-bg {
+	background: #939393;
+}
+
+.icon-img {
+	width: 88rpx;
+	height: 88rpx;
+}
+
+.img-188 {
+	width: 188rpx;
+	height: 188rpx;
+	border-radius: 16rpx;
+}
+
+.img-208 {
+	width: 208rpx;
+	height: 208rpx;
+	border-radius: 16rpx;
+}
+
+.footer-empty {
+	width: 100%;
+	height: 34rpx;
+}
+
+.width-686 {
+	width: 686rpx;
+	margin: 0 32rpx;
+}
+
+.width-616 {
+	width: 616rpx;
+}
+
+.width-355 {
+	width: 355rpx;
+}
+
+.text-align-left {
+	text-align: left;
+}
+
+.text-align-right {
+	text-align: right;
+}
+
+.font4 {
+	font-size: 4rpx;
+}
+
+.font18 {
+	font-size: 18rpx;
+}
+
+.font20 {
+	font-size: 20rpx;
+}
+
+.font22 {
+	font-size: 22rpx;
+}
+
+.font24 {
+	font-size: 24rpx;
+}
+
+.font26 {
+	font-size: 26rpx;
+}
+
+.font28 {
+	font-size: 28rpx;
+}
+
+.font30 {
+	font-size: 30rpx;
+}
+
+.font32 {
+	font-size: 32rpx;
+}
+
+.font34 {
+	font-size: 34rpx;
+}
+
+.font36 {
+	font-size: 36rpx;
+}
+
+.font38 {
+	font-size: 38rpx;
+}
+
+.font40 {
+	font-size: 40rpx;
+}
+
+.font42 {
+	font-size: 42rpx;
+}
+
+.font44 {
+	font-size: 44rpx;
+}
+
+.font46 {
+	font-size: 46rpx;
+}
+
+.font48 {
+	font-size: 48rpx;
+}
+
+.font52 {
+	font-size: 52rpx;
+}
+
+.font60 {
+	font-size: 60rpx;
+}
+
+.lineH-36 {
+	line-height: 36rpx;
+}
+
+.font-main {
+	color: #000000;
+}
+
+.dark .font-main {
+	color: #FFFFFF;
+}
+
+.font-white {
+	color: #FFFFFF;
+}
+
+.font-gray {
+	color: #a8a8a8;
+}
+
+.font-c5c {
+	color: #c5c5c5;
+}
+
+.font-darkblue {
+	color: #070032;
+}
+
+.font-blue {
+	color: #0068f0;
+}
+
+.font-red {
+	color: #FF4748;
+}
+
+.font-orchid {
+	color: #9968c3;
+}
+
+.font-green {
+	color: #53D084;
+}
+
+.font-333 {
+	color: #333333;
+}
+
+.font-707 {
+	color: #707070;
+}
+
+.font-24a {
+	color: #24afff;
+}
+
+.font-d0d {
+	color: #d0d7df;
+}
+
+.font-def {
+	color: #464646;
+}
+
+.font-8c9 {
+	color: #8c95a6;
+}
+
+.font-666 {
+	color: #666666;
+}
+
+.font-999 {
+	color: #999999;
+}
+
+.font-darkgreen {
+	color: #001C19;
+}
+
+.font-bold {
+	font-weight: bold;
+}
+
+
+
+
+
+.blue {
+	color: #9FBEDC;
+}
+
+.blue2 {
+	color: #1881d2;
+}
+
+.blue3 {
+	color: #4A5A7A;
+}
+
+.blue4 {
+	color: #8897AD;
+}
+
+.blue5 {
+	color: #013e7f;
+}
+
+.mar-32 {
+	margin: 32rpx;
+}
+
+.mar-t-5 {
+	margin-top: 5rpx;
+}
+
+.mar-t-8 {
+	margin-top: 8rpx;
+}
+
+.mar-t-10 {
+	margin-top: 10rpx;
+}
+
+.mar-t-14 {
+	margin-top: 14rpx;
+}
+
+.mar-t-16 {
+	margin-top: 16rpx;
+}
+
+.mar-t-20 {
+	margin-top: 20rpx;
+}
+
+.mar-t-24 {
+	margin-top: 24rpx;
+}
+
+.mar-t-30 {
+	margin-top: 30rpx;
+}
+
+.mar-t-32 {
+	margin-top: 32rpx;
+}
+
+.mar-t-34 {
+	margin-top: 34rpx;
+}
+
+.single-line-text {
+	text-align: center;
+	/* 强制文本在一行内显示 */
+	white-space: nowrap;
+	/* 隐藏超出容器的内容 */
+	overflow: hidden;
+	/* 超出部分显示省略号 */
+	text-overflow: ellipsis;
+	/* 设置容器宽度 */
+	width: 100rpx;
+}
+
+
+.omit-text {
+	white-space: nowrap;
+	/* 隐藏超出容器的内容 */
+	overflow: hidden;
+	/* 超出部分显示省略号 */
+	text-overflow: ellipsis;
+	width: 350rpx;
+}
+
+.mar-t-36 {
+	margin-top: 36rpx;
+}
+
+.mar-t-40 {
+	margin-top: 40rpx;
+}
+
+.mar-t-48 {
+	margin-top: 48rpx;
+}
+
+.mar-t-50 {
+	margin-top: 50rpx;
+}
+
+.mar-t-54 {
+	margin-top: 54rpx;
+}
+
+.mar-t-56 {
+	margin-top: 56rpx;
+}
+
+.mar-t-60 {
+	margin-top: 60rpx;
+}
+
+.mar-t-64 {
+	margin-top: 64rpx;
+}
+
+.mar-t-70 {
+	margin-top: 70rpx;
+}
+
+.mar-t-80 {
+	margin-top: 80rpx;
+}
+
+.mar-t-96 {
+	margin-top: 96rpx;
+}
+
+.mar-t-100 {
+	margin-top: 100rpx;
+}
+
+.mar-t-144 {
+	margin-top: 144rpx;
+}
+
+.mar-t-percent40 {
+	margin-top: 40%;
+}
+
+.mar-t-half {
+	margin-top: 50%;
+}
+
+.mar-l-2 {
+	margin-left: 2rpx;
+}
+
+.mar-l-4 {
+	margin-left: 4rpx;
+}
+
+.mar-l-6 {
+	margin-left: 6rpx;
+}
+
+.mar-l-5 {
+	margin-left: 5rpx;
+}
+
+.mar-l-8 {
+	margin-left: 8rpx;
+}
+
+.mar-l-10 {
+	margin-left: 10rpx;
+}
+
+.mar-l-auto {
+	margin-left: auto;
+}
+
+.mar-r-auto {
+	margin-right: auto;
+}
+
+.mar-l-14 {
+	margin-left: 14rpx;
+}
+
+.mar-l-16 {
+	margin-left: 16rpx;
+}
+
+.mar-l-20 {
+	margin-left: 20rpx;
+}
+
+.mar-l-24 {
+	margin-left: 24rpx;
+}
+
+.mar-l-25 {
+	margin-left: 25rpx;
+}
+
+.mar-l-30 {
+	margin-left: 30rpx;
+}
+
+.mar-l-32 {
+	margin-left: 32rpx;
+}
+
+.mar-l-40 {
+	margin-left: 40rpx;
+}
+
+.mar-l-50 {
+	margin-left: 50rpx;
+}
+
+.mar-l-52 {
+	margin-left: 52rpx;
+}
+
+.mar-l-60 {
+	margin-left: 60rpx;
+}
+
+.mar-l-70 {
+	margin-left: 70rpx;
+}
+
+.mar-l-80 {
+	margin-left: 80rpx;
+}
+
+.mar-r-8 {
+	margin-right: 8rpx;
+}
+
+.mar-r-10 {
+	margin-right: 10rpx;
+}
+
+.mar-r-14 {
+	margin-right: 14rpx;
+}
+
+.mar-r-18 {
+	margin-right: 18rpx;
+}
+
+.mar-r-20 {
+	margin-right: 20rpx;
+}
+
+.mar-r-24 {
+	margin-right: 24rpx;
+}
+
+.mar-r-30 {
+	margin-right: 30rpx;
+}
+
+.mar-r-32 {
+	margin-right: 32rpx;
+}
+
+.mar-r-40 {
+	margin-right: 40rpx;
+}
+
+.mar-r-60 {
+	margin-right: 60rpx;
+}
+
+.mar-r-250 {
+	margin-right: 250rpx;
+}
+
+.mar-r-44 {
+	margin-right: 44rpx;
+}
+
+.mar-b-16 {
+	margin-bottom: 16rpx;
+}
+
+.mar-b-3 {
+	margin-bottom: 3rpx;
+}
+
+.mar-b-20 {
+	margin-bottom: 20rpx;
+}
+
+.mar-b-24 {
+	margin-bottom: 24rpx;
+}
+
+.mar-b-30 {
+	margin-bottom: 30rpx;
+}
+
+.mar-b-50 {
+	margin-bottom: 50rpx;
+}
+
+.mar-b-60 {
+	margin-bottom: 60rpx;
+}
+
+.pad-b-100 {
+	padding-bottom: 130rpx;
+}
+
+.mar-b-32 {
+	margin-bottom: 32rpx;
+}
+
+.mar-lr-8 {
+	margin-left: 8rpx;
+	margin-right: 8rpx;
+}
+
+.mar-lr-4 {
+	margin-left: 4rpx;
+	margin-right: 4rpx;
+}
+
+.empty {
+	height: 300rpx;
+	width: 300rpx;
+}
+
+.mar-lr-12 {
+	margin-left: 12rpx;
+	margin-right: 12rpx;
+}
+
+.mar-lr-16 {
+	margin-left: 16rpx;
+	margin-right: 16rpx;
+}
+
+.shrink-0 {
+	flex-shrink: 0;
+}
+
+.mar-lr-20 {
+	margin-left: 20rpx;
+	margin-right: 20rpx;
+}
+
+.mar-lr-22 {
+	margin-left: 22rpx;
+	margin-right: 22rpx;
+}
+
+.mar-lr-24 {
+	margin-left: 24rpx;
+	margin-right: 24rpx;
+}
+
+.mar-lr-32 {
+	margin-left: 32rpx;
+	margin-right: 32rpx;
+}
+
+.mar-lr-42 {
+	margin-left: 42rpx;
+	margin-right: 42rpx;
+}
+
+.mar-lr-52 {
+	margin-left: 52rpx;
+	margin-right: 52rpx;
+}
+
+.mar-lr-64 {
+	margin-left: 64rpx;
+	margin-right: 64rpx;
+}
+
+.mar-lr-64 {
+	margin-left: 84rpx;
+	margin-right: 84rpx;
+}
+
+.mar-lr-104 {
+	margin-left: 104rpx;
+	margin-right: 104rpx;
+}
+
+.mar-tb-8 {
+	margin-top: 8rpx;
+	margin-bottom: 8rpx;
+}
+
+.mar-tb-12 {
+	margin-top: 12rpx;
+	margin-bottom: 12rpx;
+}
+
+.mar-tb-14 {
+	margin-top: 14rpx;
+	margin-bottom: 14rpx;
+}
+
+.mar-tb-16 {
+	margin-top: 16rpx;
+	margin-bottom: 16rpx;
+}
+
+.mar-tb-20 {
+	margin-top: 20rpx;
+	margin-bottom: 20rpx;
+}
+
+.mar-tb-24 {
+	margin-top: 24rpx;
+	margin-bottom: 24rpx;
+}
+
+.mar-tb-40 {
+	margin-top: 40rpx;
+	margin-bottom: 40rpx;
+}
+
+.mar-tb-32 {
+	margin-top: 32rpx;
+	margin-bottom: 32rpx;
+}
+
+.pad-l-10 {
+	padding-left: 10rpx;
+}
+
+.pad-l-20 {
+	padding-left: 20rpx;
+}
+
+.pad-l-32 {
+	padding-left: 32rpx;
+}
+
+.pad-l-40 {
+	padding-left: 40rpx;
+}
+
+.pad-l-50 {
+	padding-left: 40rpx;
+}
+
+.pad-r-4 {
+	padding-right: 4rpx;
+}
+
+.pad-r-8 {
+	padding-right: 8rpx;
+}
+
+.pad-r-10 {
+	padding-right: 10rpx;
+}
+
+.pad-r-20 {
+	padding-right: 20rpx;
+}
+
+.pad-r-32 {
+	padding-right: 32rpx;
+}
+
+.pad-r-40 {
+	padding-right: 40rpx;
+}
+
+.pad-t-20 {
+	padding-top: 20rpx;
+}
+
+.pad-t-16 {
+	padding-top: 16rpx;
+}
+
+.pad-t-26 {
+	padding-top: 26rpx;
+}
+
+.pad-t-28 {
+	padding-top: 28rpx;
+}
+
+.pad-t-32 {
+	padding-top: 32rpx;
+}
+
+.pad-t-40 {
+	padding-top: 40rpx;
+}
+
+.pad-b-16 {
+	padding-bottom: 16rpx;
+}
+
+.pad-b-20 {
+	padding-bottom: 20rpx;
+}
+
+.pad-b-24 {
+	padding-bottom: 24rpx;
+}
+
+.pad-tb-4 {
+	padding-top: 4rpx;
+	padding-bottom: 4rpx;
+}
+
+.pad-tb-6 {
+	padding-top: 6rpx;
+	padding-bottom: 6rpx;
+}
+
+.pad-tb-8 {
+	padding-top: 8rpx;
+	padding-bottom: 8rpx;
+}
+
+.pad-tb-16 {
+	padding-top: 16rpx;
+	padding-bottom: 16rpx;
+}
+
+.gray-line {
+	height: 1px;
+	/* 线条高度 */
+	background-color: #f9f9f9;
+	/* 浅灰色,可根据需要调整颜色值 */
+	width: 100%;
+	/* 线条宽度,这里设置为占满父容器 */
+	margin-top: 8rpx;
+	margin-bottom: 8rpx;
+}
+
+.pad-tb-20 {
+	padding-top: 20rpx;
+	padding-bottom: 20rpx;
+}
+
+.pad-tb-22 {
+	padding-top: 22rpx;
+	padding-bottom: 22rpx;
+}
+
+.pad-tb-24 {
+	padding-top: 24rpx;
+	padding-bottom: 24rpx;
+}
+
+.pad-tb-30 {
+	padding-top: 30rpx;
+	padding-bottom: 30rpx;
+}
+
+.pad-tb-32 {
+	padding-top: 32rpx;
+	padding-bottom: 32rpx;
+}
+
+.pad-tb-40 {
+	padding-top: 40rpx;
+	padding-bottom: 40rpx;
+}
+
+.pad-tb-50 {
+	padding: 50rpx 0rpx;
+}
+
+.pad-lr-5 {
+	padding: 0rpx 5rpx;
+}
+
+.pad-lr-10 {
+	padding-left: 10rpx;
+	padding-right: 10rpx;
+}
+
+.pad-lr-12 {
+	padding-left: 12rpx;
+	padding-right: 12rpx;
+}
+
+.pad-lr-14 {
+	padding-left: 14rpx;
+	padding-right: 14rpx;
+}
+
+.pad-lr-16 {
+	padding-left: 16rpx;
+	padding-right: 16rpx;
+}
+
+.pad-lr-18 {
+	padding-left: 18rpx;
+	padding-right: 18rpx;
+}
+
+.pad-lr-20 {
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+}
+
+.pad-lr-24 {
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+}
+
+.pad-lr-32 {
+	padding-left: 32rpx;
+	padding-right: 32rpx;
+}
+
+.pad-lr-48 {
+	padding-left: 48rpx;
+	padding-right: 48rpx;
+}
+
+.pad-b-8 {
+	padding-bottom: 8rpx;
+}
+
+.pad-b-30 {
+	padding-bottom: 30rpx;
+}
+
+.pad-b-32 {
+	padding-bottom: 32rpx;
+}
+
+.pad-b-40 {
+	padding-bottom: 40rpx;
+}
+
+.pad-b-130 {
+	padding-bottom: 130rpx;
+}
+
+.pad-b-140 {
+	padding-bottom: 140rpx;
+}
+
+.pad-16 {
+	padding: 16rpx;
+}
+
+.pad-24 {
+	padding: 24rpx;
+}
+
+.pad-32 {
+	padding: 32rpx;
+}
+
+.pos-r {
+	position: relative;
+}
+
+.pos-abs {
+	position: absolute;
+}
+
+.pos-fixed {
+	position: fixed;
+}
+
+.flex {
+	display: flex;
+}
+
+.flex-1 {
+	flex: 1;
+}
+
+.flex-2 {
+	flex: 2;
+}
+
+.flex-3 {
+	flex: 3;
+}
+
+.flex-4 {
+	flex: 4;
+}
+
+.flex-items {
+	display: flex;
+	align-items: center;
+}
+
+.nowrap {
+	white-space: nowrap
+}
+
+.flex-items-between {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.flex-items-plus {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.flex-items-start {
+	display: flex;
+	justify-content: start;
+	align-items: center;
+}
+
+.flex-start {
+	display: flex;
+	justify-content: flex-start;
+}
+
+.flex-end {
+	display: flex;
+	justify-content: flex-end;
+}
+
+.flex-end-plus {
+	display: flex;
+	justify-content: flex-end;
+	align-items: center;
+}
+
+.flex-column {
+	flex-direction: column
+}
+
+.flex-column-plus {
+	display: flex;
+	flex-direction: column;
+}
+
+.self-baseline {
+	align-self: baseline;
+}
+
+.flex-row {
+	flex-direction: row
+}
+
+.flex-row-plus {
+	display: flex;
+	flex-direction: row
+}
+
+.flex-sp-around {
+	justify-content: space-around;
+}
+
+.flex-sp-between {
+	justify-content: space-between;
+}
+
+.flex-align-beteen {
+	align-content: space-between;
+}
+
+.text-align {
+	text-align: center;
+}
+
+.flex-wrap-1 {
+	display: flex;
+	flex-wrap: wrap
+}
+
+.flex-nowrap-1 {
+	display: flex;
+	flex-wrap: nowrap
+}
+
+.align-end {
+	display: flex;
+	align-items: flex-end;
+}
+
+.align-start {
+	display: flex;
+	align-items: flex-start;
+}
+
+.line-w {
+	width: 750rpx;
+	height: 4rpx;
+	background-color: #EEEEEE
+}
+
+.line-h-48 {
+	width: 4rpx;
+	height: 48rpx;
+	background-color: #EEEEEE
+}
+
+.overflow {
+	overflow: hidden;
+}
+
+.chao1 {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.chao2 {
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-box-pack: center;
+	-webkit-box-align: center;
+	-webkit-line-clamp: 2;
+	overflow: hidden;
+}
+
+.chao3 {
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-box-pack: center;
+	-webkit-box-align: center;
+	-webkit-line-clamp: 3;
+	overflow: hidden;
+}
+
+.border {
+	border: #EEEEEE solid 1px;
+}
+
+.border-l-dashed {
+	border-left: #EEEEEE dashed 1px;
+}
+
+.border-t-dashed {
+	border-top: #EEEEEE dashed 1px;
+}
+
+.border-r-dashed {
+	border-right: #EEEEEE dashed 1px;
+}
+
+.border-b-dashed {
+	border-bottom: #EEEEEE dashed 1px;
+}
+
+.border-l {
+	border-left: #EEEEEE solid 1px;
+}
+
+.border-t {
+	border-top: #EEEEEE solid 1px;
+}
+
+.border-r {
+	border-right: #EEEEEE solid 1px;
+}
+
+.border-b {
+	border-bottom: #EEEEEE solid 1px;
+}
+
+/**
+ * 公共搜索模块样式
+ */
+.global-search {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 32rpx;
+	position: relative;
+	z-index: 99;
+	background: #F7F7F7;
+}
+
+.global-search .u-input {
+	background: #ffffff;
+	box-shadow: 0px 2px 28px 2px rgba(0, 0, 0, 0.1);
+}
+
+.global-search .search-right {
+	width: 40rpx;
+	height: 40rpx;
+	margin-left: 24rpx;
+	background: #3f3baf;
+	color: #ffffff;
+	font-weight: 500;
+	border-radius: 20rpx;
+	font-size: 52rpx;
+	line-height: 33rpx;
+	text-align: center;
+}
+
+.global-tag {
+	padding: 0 20rpx;
+	margin-left: 24rpx;
+	border-radius: 8rpx;
+	height: 40rpx;
+	line-height: 40rpx;
+}
+
+.tag-box {
+	width: 112rpx;
+	height: 40rpx;
+	margin-left: 14rpx;
+}
+
+.tag-box-auto {
+	padding: 0 20rpx;
+	height: 40rpx;
+	margin-left: 14rpx;
+}
+
+/* 选框尺寸公共样式 */
+.filter-selete-box-xs {
+	width: 112rpx;
+	height: 40rpx;
+	overflow: hidden;
+	background: #F2F1FF;
+	border: 2rpx solid #3F3BAF;
+}
+
+.filter-selete-box-xs.active {
+	background: #3F3BAF;
+}
+
+.filter-selete-box-xs.active text {
+	color: #FFFFFF;
+}
+
+/* .filter-selete-box-small{
+	width: 168rpx;
+	height: 44rpx;
+	overflow: hidden;
+	background: #F2F1FF;
+	border: 2rpx solid #3F3BAF;
+}
+.filter-selete-box-small.active{
+	background: #3F3BAF;
+}
+.filter-selete-box-small.active text{
+	color: #FFFFFF;
+} */
+
+/* .filter-selete-box-normal{
+	width: 172rpx;
+	height: 96rpx;
+	overflow: hidden;
+	background: #F2F1FF;
+	border: 2rpx solid #3F3BAF;
+}
+.filter-selete-box-normal.active{
+	background: linear-gradient(314deg, #4E4BBB 0%, #B9B7FF 100%);
+	border: none;
+}
+.filter-selete-box-normal.active text{
+	color: #FFFFFF;
+}
+
+.filter-selete-box-large{
+	width: 272rpx;
+	height: 96rpx;
+	overflow: hidden;
+	background: #F2F1FF;
+	border: 2rpx solid #3F3BAF;
+}
+.filter-selete-box-large.active{
+	background: linear-gradient(314deg, #4E4BBB 0%, #B9B7FF 100%);
+	border: none;
+}
+.filter-selete-box-large.active text{
+	color: #FFFFFF;
+} */
+
+.selected,
+.not-selected {
+	width: 44rpx;
+	height: 44rpx;
+	margin-right: 16rpx;
+}
+
+.not-selected {
+	border: 2rpx solid #D6D6D6;
+	border-radius: 50%;
+}
+
+.wrap {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	z-index: 999;
+}
+
+/* start--iPhoneX底部安全区定义--start */
+.safe-area-inset-bottom {
+	padding-bottom: 0;
+	padding-bottom: constant(safe-area-inset-bottom);
+	padding-bottom: env(safe-area-inset-bottom);
+}
+
+/* end-iPhoneX底部安全区定义--end */
+/* start--去除button的所有默认样式--start */
+.u-reset-button {
+	padding: 0;
+	margin: 0;
+	font-size: inherit;
+	line-height: inherit;
+	background-color: transparent;
+	color: inherit;
+	transform: translate(0rpx, 0rpx);
+}
+
+.u-reset-button.button-hover {
+	transform: translate(1upx, 1upx);
+}
+
+.u-reset-button::after {
+	border: none;
+}
+
+/* end--去除button的所有默认样式--end */

+ 1458 - 0
common/uni.css

@@ -0,0 +1,1458 @@
+@font-face {
+	font-family: uniicons;
+	font-weight: normal;
+	font-style: normal;
+	src: url('~@/static/uni.ttf') format('truetype');
+}
+
+/* #ifdef H5 */
+.fix-left-window {
+	padding-left: var(--window-left);
+}
+.pc-hide {
+	display: none !important;
+}
+/* #endif */
+
+/*通用 */
+
+/* view{
+	font-size:28rpx;
+	line-height:1.8;
+} */
+progress, checkbox-group{
+	width: 100%;
+}
+form {
+	width: 100%;
+}
+.uni-flex {
+	display: flex;
+	flex-direction: row;
+}
+.uni-flex-item {
+	flex: 1;
+}
+.uni-row {
+	flex-direction: row;
+}
+.uni-column {
+	flex-direction: column;
+}
+.uni-link{
+	color:#576B95;
+	font-size:26rpx;
+}
+.uni-center{
+	text-align:center;
+}
+.uni-inline-item{
+	display: flex;
+	flex-direction: row;
+	align-items:center;
+}
+.uni-inline-item text{
+	margin-right: 20rpx;
+}
+.uni-inline-item text:last-child{
+	margin-right: 0rpx;
+	margin-left: 20rpx;
+}
+
+/* page */
+.common-page-head{
+	padding:35rpx;
+	text-align: center;
+}
+.common-page-head-title {
+	display: inline-block;
+	padding: 0 40rpx;
+	font-size: 30rpx;
+	height: 88rpx;
+	line-height: 88rpx;
+	color: #BEBEBE;
+	box-sizing: border-box;
+	border-bottom: 2rpx solid #D8D8D8;
+}
+
+.uni-padding-wrap{
+	/* width:690rpx; */
+	padding:0 30rpx;
+}
+.uni-word {
+	text-align: center;
+	padding:200rpx 100rpx;
+}
+.uni-title {
+	font-size:30rpx;
+	font-weight:500;
+	padding:20rpx 0;
+	line-height:1.5;
+}
+.uni-text{
+	font-size:28rpx;
+}
+.uni-title text{
+	font-size:24rpx;
+	color:#888;
+}
+
+.uni-text-gray{
+	color: #ccc;
+}
+.uni-text-small {
+	font-size:24rpx;
+}
+.uni-common-mb{
+	margin-bottom:30rpx;
+}
+.uni-common-pb{
+	padding-bottom:30rpx;
+}
+.uni-common-pl{
+	padding-left:30rpx;
+}
+.uni-common-mt{
+	margin-top:30rpx;
+}
+/* 背景色 */
+.uni-bg-red{
+	background:#F76260; color:#FFF;
+}
+.uni-bg-green{
+	background:#09BB07; color:#FFF;
+}
+.uni-bg-blue{
+	background:#007AFF; color:#FFF;
+}
+/* 标题 */
+.uni-h1 {font-size: 80rpx; font-weight:700;}
+.uni-h2 {font-size: 60rpx; font-weight:700;}
+.uni-h3 {font-size: 48rpx; font-weight:700;}
+.uni-h4 {font-size: 36rpx; font-weight:700;}
+.uni-h5 {font-size: 28rpx; color: #8f8f94;}
+.uni-h6 {font-size: 24rpx; color: #8f8f94;}
+.uni-bold{font-weight:bold;}
+
+/* 文本溢出隐藏 */
+.uni-ellipsis {overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
+
+/* 竖向百分百按钮 */
+.uni-btn-v{
+	padding:10rpx 0;
+}
+.uni-btn-v button{margin:20rpx 0;}
+
+/* 表单 */
+.uni-form-item{
+	display:flex;
+	width:100%;
+	padding:10rpx 0;
+}
+.uni-form-item .title{
+	padding:10rpx 25rpx;
+}
+.uni-label {
+	width: 210rpx;
+	word-wrap: break-word;
+	word-break: break-all;
+	text-indent:20rpx;
+}
+.uni-input {
+	height: 50rpx;
+	padding: 15rpx 25rpx;
+	line-height:50rpx;
+	font-size:28rpx;
+	background:#FFF;
+	flex: 1;
+}
+radio-group, checkbox-group{
+	width:100%;
+}
+radio-group label, checkbox-group label{
+	padding-right:20rpx;
+}
+.uni-form-item .with-fun{
+	display:flex;
+	flex-wrap:nowrap;
+	background:#FFFFFF;
+}
+.uni-form-item .with-fun .uni-icon{
+	width:40px;
+	height:80rpx;
+	line-height:80rpx;
+	flex-shrink:0;
+}
+
+/* loadmore */
+.uni-loadmore{
+	height:80rpx;
+	line-height:80rpx;
+	text-align:center;
+	padding-bottom:30rpx;
+}
+/*数字角标*/
+/* .uni-badge,
+.uni-badge-default {
+	font-family: 'Helvetica Neue', Helvetica, sans-serif;
+	font-size: 12px;
+	line-height: 1;
+	display: inline-block;
+	padding: 3px 6px;
+	color: #333;
+	border-radius: 100px;
+	background-color: rgba(0, 0, 0, .15);
+} */
+.uni-badge.uni-badge-inverted {
+	padding: 0 5px 0 0;
+	color: #929292;
+	background-color: transparent
+}
+.uni-badge-primary {
+	color: #fff;
+	background-color: #007aff
+}
+.uni-badge-blue.uni-badge-inverted,
+.uni-badge-primary.uni-badge-inverted {
+	color: #007aff;
+	background-color: transparent
+}
+.uni-badge-green,
+.uni-badge-success {
+	color: #fff;
+	background-color: #4cd964;
+}
+.uni-badge-green.uni-badge-inverted,
+.uni-badge-success.uni-badge-inverted {
+	color: #4cd964;
+	background-color: transparent
+}
+.uni-badge-warning,
+.uni-badge-yellow {
+	color: #fff;
+	background-color: #f0ad4e
+}
+.uni-badge-warning.uni-badge-inverted,
+.uni-badge-yellow.uni-badge-inverted {
+	color: #f0ad4e;
+	background-color: transparent
+}
+.uni-badge-danger,
+.uni-badge-red {
+	color: #fff;
+	background-color: #dd524d
+}
+.uni-badge-danger.uni-badge-inverted,
+.uni-badge-red.uni-badge-inverted {
+	color: #dd524d;
+	background-color: transparent
+}
+.uni-badge-purple,
+.uni-badge-royal {
+	color: #fff;
+	background-color: #8a6de9
+}
+.uni-badge-purple.uni-badge-inverted,
+.uni-badge-royal.uni-badge-inverted {
+	color: #8a6de9;
+	background-color: transparent
+}
+
+/*折叠面板 */
+.uni-collapse-content {
+	height: 0;
+	width: 100%;
+	overflow: hidden;
+}
+.uni-collapse-content.uni-active {
+	height: auto;
+}
+
+/*卡片视图 */
+.uni-card {
+	background: #fff;
+	border-radius: 8rpx;
+	margin:20rpx 0;
+	position: relative;
+	/* box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, .3); */
+}
+.uni-card-content {
+	font-size: 30rpx;
+}
+.uni-card-content.image-view{
+    width: 100%;
+    margin: 0;
+}
+.uni-card-content-inner {
+	position: relative;
+	padding: 30rpx;
+}
+.uni-card-footer,
+.uni-card-header {
+	position: relative;
+	display: flex;
+	min-height: 50rpx;
+	padding: 20rpx 30rpx;
+	justify-content: space-between;
+	align-items: center;
+}
+.uni-card-header {
+	font-size: 36rpx;
+}
+.uni-card-footer {
+	color: #6d6d72;
+}
+.uni-card-footer:before,
+.uni-card-header:after {
+	position: absolute;
+	top: 0;
+	right: 0;
+	left: 0;
+	height: 2rpx;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+.uni-card-header:after {
+	top: auto;
+	bottom: 0;
+}
+.uni-card-media {
+	justify-content: flex-start;
+}
+.uni-card-media-logo {
+	height: 84rpx;
+	width: 84rpx;
+	margin-right: 20rpx;
+}
+.uni-card-media-body {
+	height: 84rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	align-items: flex-start;
+}
+.uni-card-media-text-top {
+	line-height: 36rpx;
+	font-size: 34rpx;
+}
+.uni-card-media-text-bottom {
+	line-height: 30rpx;
+	font-size: 28rpx;
+	color: #8f8f94;
+}
+.uni-card-link {
+	color: #007AFF;
+}
+
+/* 列表 */
+.uni-list {
+	background-color: #FFFFFF;
+	position: relative;
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+}
+.uni-list:after {
+	position: absolute;
+	z-index: 10;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	height: 1px;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+/* .uni-list::before {
+	position: absolute;
+	z-index: 10;
+	right: 0;
+	top: 0;
+	left: 0;
+	height: 1px;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+} */
+.uni-list-cell {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+.uni-list-cell-hover {
+	background-color: #eee;
+}
+.uni-list-cell-pd {
+	padding: 22rpx 30rpx;
+}
+.uni-list-cell-left {
+    white-space: nowrap;
+	font-size:28rpx;
+	padding: 0 30rpx;
+}
+.uni-list-cell-db,
+.uni-list-cell-right {
+	flex: 1;
+}
+.uni-list-cell::after {
+	position: absolute;
+	z-index: 3;
+	right: 0;
+	bottom: 0;
+	left: 30rpx;
+	height: 1px;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+.uni-list .uni-list-cell:last-child::after {
+	height: 0rpx;
+}
+.uni-list-cell-last.uni-list-cell::after {
+	height: 0rpx;
+}
+.uni-list-cell-divider {
+	position: relative;
+	display: flex;
+	color: #999;
+	background-color: #f7f7f7;
+	padding:15rpx 20rpx;
+}
+.uni-list-cell-divider::before {
+	position: absolute;
+	right: 0;
+	top: 0;
+	left: 0;
+	height: 1px;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+.uni-list-cell-divider::after {
+	position: absolute;
+	right: 0;
+	bottom: 0;
+	left: 0rpx;
+	height: 1px;
+	content: '';
+	-webkit-transform: scaleY(.5);
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+.uni-list-cell-navigate {
+	font-size:30rpx;
+	padding: 22rpx 30rpx;
+	line-height: 48rpx;
+	position: relative;
+	display: flex;
+	box-sizing: border-box;
+	width: 100%;
+	flex: 1;
+	justify-content: space-between;
+	align-items: center;
+}
+.uni-list-cell-navigate {
+	padding-right: 36rpx;
+}
+.uni-navigate-badge {
+	padding-right: 50rpx;
+}
+.uni-list-cell-navigate.uni-navigate-right:after {
+	font-family: uniicons;
+	content: '\e583';
+	position: absolute;
+	right: 24rpx;
+	top: 50%;
+	color: #bbb;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+}
+.uni-list-cell-navigate.uni-navigate-bottom:after {
+	font-family: uniicons;
+	content: '\e581';
+	position: absolute;
+	right: 24rpx;
+	top: 50%;
+	color: #bbb;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+}
+.uni-list-cell-navigate.uni-navigate-bottom.uni-active::after {
+	font-family: uniicons;
+	content: '\e580';
+	position: absolute;
+	right: 24rpx;
+	top: 50%;
+	color: #bbb;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+}
+.uni-collapse.uni-list-cell {
+	flex-direction: column;
+}
+.uni-list-cell-navigate.uni-active {
+	background: #eee;
+}
+.uni-list.uni-collapse {
+	box-sizing: border-box;
+	height: 0;
+	overflow: hidden;
+}
+.uni-collapse .uni-list-cell {
+	padding-left: 20rpx;
+}
+.uni-collapse .uni-list-cell::after {
+	left: 52rpx;
+}
+.uni-list.uni-active {
+	height: auto;
+}
+
+/* 三行列表 */
+.uni-triplex-row {
+	display: flex;
+	flex: 1;
+	width: 100%;
+	box-sizing: border-box;
+	flex-direction: row;
+	padding: 22rpx 30rpx;
+}
+.uni-triplex-right,
+.uni-triplex-left {
+	display: flex;
+	flex-direction: column;
+}
+.uni-triplex-left {
+	width: 84%;
+}
+.uni-triplex-left .uni-title{
+	padding:8rpx 0;
+}
+.uni-triplex-left .uni-text, .uni-triplex-left .uni-text-small{color:#999999;}
+.uni-triplex-right {
+	width: 16%;
+	text-align: right;
+}
+
+/* 图文列表 */
+.uni-media-list {
+	padding: 22rpx 30rpx;
+	box-sizing: border-box;
+	display: flex;
+	width: 100%;
+	flex-direction: row;
+}
+.uni-navigate-right.uni-media-list {
+	padding-right: 74rpx;
+}
+.uni-pull-right {
+	flex-direction: row-reverse;
+}
+.uni-pull-right>.uni-media-list-logo {
+	margin-right: 0rpx;
+	margin-left: 20rpx;
+}
+.uni-media-list-logo {
+	height: 84rpx;
+	width: 84rpx;
+	margin-right: 20rpx;
+}
+.uni-media-list-logo image {
+	height: 100%;
+	width: 100%;
+}
+.uni-media-list-body {
+	height: 84rpx;
+	display: flex;
+	flex: 1;
+	flex-direction: column;
+	justify-content: space-between;
+	align-items: flex-start;
+	overflow: hidden;
+}
+.uni-media-list-text-top {
+	width: 100%;
+	line-height: 36rpx;
+	font-size: 30rpx;
+}
+.uni-media-list-text-bottom {
+	width: 100%;
+	line-height: 30rpx;
+	font-size: 26rpx;
+	color: #8f8f94;
+}
+
+/* 九宫格 */
+.uni-grid-9 {
+	background: #f2f2f2;
+	width: 750rpx;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	border-top: 2rpx solid #eee;
+}
+.uni-grid-9-item {
+	width: 250rpx;
+	height: 200rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	border-bottom: 2rpx solid;
+	border-right: 2rpx solid;
+	border-color: #eee;
+	box-sizing: border-box;
+}
+.no-border-right {
+	border-right: none;
+}
+.uni-grid-9-image {
+	width: 100rpx;
+	height: 100rpx;
+}
+.uni-grid-9-text {
+	width: 250rpx;
+	line-height: 4rpx;
+	height: 40rpx;
+	text-align: center;
+	font-size: 30rpx;
+}
+.uni-grid-9-item-hover {
+	background: rgba(0, 0, 0, 0.1);
+}
+
+/* 上传 */
+.uni-uploader {
+	flex: 1;
+	flex-direction: column;
+}
+.uni-uploader-head {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+}
+.uni-uploader-info {
+	color: #B2B2B2;
+}
+.uni-uploader-body {
+	margin-top: 16rpx;
+}
+.uni-uploader__files {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+.uni-uploader__file {
+	margin: 10rpx;
+	width: 210rpx;
+	height: 210rpx;
+}
+.uni-uploader__img {
+	display: block;
+	width: 210rpx;
+	height: 210rpx;
+}
+.uni-uploader__input-box {
+	position: relative;
+	margin:10rpx;
+	width: 208rpx;
+	height: 208rpx;
+	border: 2rpx solid #D9D9D9;
+}
+.uni-uploader__input-box:before,
+.uni-uploader__input-box:after {
+	content: " ";
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: translate(-50%, -50%);
+	transform: translate(-50%, -50%);
+	background-color: #D9D9D9;
+}
+.uni-uploader__input-box:before {
+	width: 4rpx;
+	height: 79rpx;
+}
+.uni-uploader__input-box:after {
+	width: 79rpx;
+	height: 4rpx;
+}
+.uni-uploader__input-box:active {
+	border-color: #999999;
+}
+.uni-uploader__input-box:active:before,
+.uni-uploader__input-box:active:after {
+	background-color: #999999;
+}
+.uni-uploader__input {
+	position: absolute;
+	z-index: 1;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	opacity: 0;
+}
+
+/*问题反馈*/
+.feedback-title {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	padding: 20rpx;
+	color: #8f8f94;
+	font-size: 28rpx;
+}
+.feedback-star-view.feedback-title {
+	justify-content: flex-start;
+	margin: 0;
+}
+.feedback-quick {
+	position: relative;
+	padding-right: 40rpx;
+}
+.feedback-quick:after {
+	font-family: uniicons;
+	font-size: 40rpx;
+	content: '\e581';
+	position: absolute;
+	right: 0;
+	top: 50%;
+	color: #bbb;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+}
+.feedback-body {
+	background: #fff;
+}
+.feedback-textare {
+	height: 200rpx;
+	font-size: 34rpx;
+	line-height: 50rpx;
+	width: 100%;
+	box-sizing: border-box;
+	padding: 20rpx 30rpx 0;
+}
+.feedback-input {
+	font-size: 34rpx;
+	height: 50rpx;
+	min-height: 50rpx;
+	padding: 15rpx 20rpx;
+	line-height: 50rpx;
+}
+.feedback-uploader {
+	padding: 22rpx 20rpx;
+}
+.feedback-star {
+	font-family: uniicons;
+	font-size: 40rpx;
+	margin-left: 6rpx;
+}
+.feedback-star-view {
+	margin-left: 20rpx;
+}
+.feedback-star:after {
+	content: '\e408';
+}
+.feedback-star.active {
+	color: #FFB400;
+}
+.feedback-star.active:after {
+	content: '\e438';
+}
+.feedback-submit {
+	background: #007AFF;
+	color: #FFFFFF;
+	margin: 20rpx;
+}
+
+/* input group */
+.uni-input-group {
+	position: relative;
+	padding: 0;
+	border: 0;
+	background-color: #fff;
+}
+
+.uni-input-group:before {
+	position: absolute;
+	top: 0;
+	right: 0;
+	left: 0;
+	height: 2rpx;
+	content: '';
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+
+.uni-input-group:after {
+	position: absolute;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	height: 2rpx;
+	content: '';
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+
+.uni-input-row {
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	font-size:28rpx;
+	padding: 22rpx 30rpx;
+	justify-content: space-between;
+}
+
+.uni-input-group .uni-input-row:after {
+	position: absolute;
+	right: 0;
+	bottom: 0;
+	left: 30rpx;
+	height: 2rpx;
+	content: '';
+	transform: scaleY(.5);
+	background-color: #c8c7cc;
+}
+
+.uni-input-row label {
+	line-height: 70rpx;
+}
+
+/* textarea */
+.uni-textarea{
+	width:100%;
+	background:#FFF;
+}
+.uni-textarea textarea{
+	width:96%;
+	padding:18rpx 2%;
+	line-height:1.6;
+	font-size:28rpx;
+	height:150rpx;
+}
+
+/* tab bar */
+.uni-tab-bar {
+	display: flex;
+	flex: 1;
+	flex-direction: column;
+	overflow: hidden;
+	height: 100%;
+}
+
+.uni-tab-bar .list {
+	width: 750rpx;
+	height: 100%;
+}
+
+.uni-swiper-tab {
+	width: 100%;
+	white-space: nowrap;
+	line-height: 100rpx;
+	height: 100rpx;
+	border-bottom: 1px solid #c8c7cc;
+}
+
+.swiper-tab-list {
+	font-size: 30rpx;
+	width: 150rpx;
+	display: inline-block;
+	text-align: center;
+	color: #555;
+}
+
+.uni-tab-bar .active {
+	color: #007AFF;
+}
+
+.uni-tab-bar .swiper-box {
+	flex: 1;
+	width: 100%;
+	height: calc(100% - 100rpx);
+}
+
+.uni-tab-bar-loading{
+	padding:20rpx 0;
+}
+
+/* comment */
+.uni-comment{padding:5rpx 0; display: flex; flex-grow:1; flex-direction: column;}
+.uni-comment-list{flex-wrap:nowrap; padding:10rpx 0; margin:10rpx 0; width:100%; display: flex;}
+.uni-comment-face{width:70rpx; height:70rpx; border-radius:100%; margin-right:20rpx; flex-shrink:0; overflow:hidden;}
+.uni-comment-face image{width:100%; border-radius:100%;}
+.uni-comment-body{width:100%;}
+.uni-comment-top{line-height:1.5em; justify-content:space-between;}
+.uni-comment-top text{color:#0A98D5; font-size:24rpx;}
+.uni-comment-date{line-height:38rpx; flex-direction:row; justify-content:space-between; display:flex !important; flex-grow:1;}
+.uni-comment-date view{color:#666666; font-size:24rpx; line-height:38rpx;}
+.uni-comment-content{line-height:1.6em; font-size:28rpx; padding:8rpx 0;}
+.uni-comment-replay-btn{background:#FFF; font-size:24rpx; line-height:28rpx; padding:5rpx 20rpx; border-radius:30rpx; color:#333 !important; margin:0 10rpx;}
+
+/* swiper msg */
+.uni-swiper-msg{width:100%; padding:12rpx 0; flex-wrap:nowrap; display:flex;}
+.uni-swiper-msg-icon{width:50rpx; margin-right:20rpx;}
+.uni-swiper-msg-icon image{width:100%; flex-shrink:0;}
+.uni-swiper-msg swiper{width:100%; height:50rpx;}
+.uni-swiper-msg swiper-item{line-height:50rpx;}
+
+/* product */
+.uni-product-list {
+    display: flex;
+    width: 100%;
+    flex-wrap: wrap;
+    flex-direction: row;
+}
+
+.uni-product {
+    padding: 20rpx;
+    display: flex;
+    flex-direction: column;
+}
+
+.image-view {
+    height: 330rpx;
+    width: 330rpx;
+	margin:12rpx 0;
+}
+
+.uni-product-image {
+    height: 330rpx;
+    width: 330rpx;
+}
+
+.uni-product-title {
+    width: 300rpx;
+    word-break: break-all;
+    display: -webkit-box;
+    overflow: hidden;
+	line-height:1.5;
+    text-overflow: ellipsis;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+}
+
+.uni-product-price {
+	margin-top:10rpx;
+    font-size: 28rpx;
+	line-height:1.5;
+    position: relative;
+}
+
+.uni-product-price-original {
+    color: #e80080;
+}
+
+.uni-product-price-favour {
+    color: #888888;
+    text-decoration: line-through;
+    margin-left: 10rpx;
+}
+
+.uni-product-tip {
+    position: absolute;
+    right: 10rpx;
+    background-color: #ff3333;
+    color: #ffffff;
+    padding: 0 10rpx;
+    border-radius: 5rpx;
+}
+
+/* timeline */
+.uni-timeline {
+		margin: 35rpx 0;
+		display: flex;
+		flex-direction: column;
+		position: relative;
+	}
+
+
+	.uni-timeline-item {
+		display: flex;
+		flex-direction: row;
+		position: relative;
+		padding-bottom: 20rpx;
+		box-sizing: border-box;
+		overflow: hidden;
+
+	}
+
+	.uni-timeline-item .uni-timeline-item-keynode {
+		width: 160rpx;
+		flex-shrink: 0;
+		box-sizing: border-box;
+		padding-right: 20rpx;
+		text-align: right;
+		line-height: 65rpx;
+	}
+
+	.uni-timeline-item .uni-timeline-item-divider {
+		flex-shrink: 0;
+		position: relative;
+		width: 30rpx;
+		height: 30rpx;
+		top: 15rpx;
+		border-radius: 50%;
+		background-color: #bbb;
+	}
+
+
+
+	.uni-timeline-item-divider::before,
+	.uni-timeline-item-divider::after {
+		position: absolute;
+		left: 15rpx;
+		width: 1rpx;
+		height: 100vh;
+		content: '';
+		background: inherit;
+	}
+
+	.uni-timeline-item-divider::before {
+		bottom: 100%;
+	}
+
+	.uni-timeline-item-divider::after {
+		top: 100%;
+	}
+
+
+	.uni-timeline-last-item .uni-timeline-item-divider:after {
+		display: none;
+	}
+
+	.uni-timeline-first-item .uni-timeline-item-divider:before {
+		display: none;
+	}
+
+	.uni-timeline-item .uni-timeline-item-content {
+		padding-left: 20rpx;
+	}
+
+	.uni-timeline-last-item .bottom-border::after{
+		display: none;
+	}
+
+	.uni-timeline-item-content .datetime{
+		color: #CCCCCC;
+	}
+
+	/* 自定义节点颜色 */
+	.uni-timeline-last-item .uni-timeline-item-divider{
+		background-color: #1AAD19;
+	}
+
+
+/* uni-icon */
+
+.uni-icon {
+	font-family: uniicons;
+	font-size: 24px;
+	font-weight: normal;
+	font-style: normal;
+	line-height: 1;
+	display: inline-block;
+	text-decoration: none;
+	-webkit-font-smoothing: antialiased;
+}
+
+.uni-icon.uni-active {
+	color: #007aff;
+}
+
+.uni-icon-contact:before {
+	content: '\e100';
+}
+
+.uni-icon-person:before {
+	content: '\e101';
+}
+
+.uni-icon-personadd:before {
+	content: '\e102';
+}
+
+.uni-icon-contact-filled:before {
+	content: '\e130';
+}
+
+.uni-icon-person-filled:before {
+	content: '\e131';
+}
+
+.uni-icon-personadd-filled:before {
+	content: '\e132';
+}
+
+.uni-icon-phone:before {
+	content: '\e200';
+}
+
+.uni-icon-email:before {
+	content: '\e201';
+}
+
+.uni-icon-chatbubble:before {
+	content: '\e202';
+}
+
+.uni-icon-chatboxes:before {
+	content: '\e203';
+}
+
+.uni-icon-phone-filled:before {
+	content: '\e230';
+}
+
+.uni-icon-email-filled:before {
+	content: '\e231';
+}
+
+.uni-icon-chatbubble-filled:before {
+	content: '\e232';
+}
+
+.uni-icon-chatboxes-filled:before {
+	content: '\e233';
+}
+
+.uni-icon-weibo:before {
+	content: '\e260';
+}
+
+.uni-icon-weixin:before {
+	content: '\e261';
+}
+
+.uni-icon-pengyouquan:before {
+	content: '\e262';
+}
+
+.uni-icon-chat:before {
+	content: '\e263';
+}
+
+.uni-icon-qq:before {
+	content: '\e264';
+}
+
+.uni-icon-videocam:before {
+	content: '\e300';
+}
+
+.uni-icon-camera:before {
+	content: '\e301';
+}
+
+.uni-icon-mic:before {
+	content: '\e302';
+}
+
+.uni-icon-location:before {
+	content: '\e303';
+}
+
+.uni-icon-mic-filled:before,
+.uni-icon-speech:before {
+	content: '\e332';
+}
+
+.uni-icon-location-filled:before {
+	content: '\e333';
+}
+
+.uni-icon-micoff:before {
+	content: '\e360';
+}
+
+.uni-icon-image:before {
+	content: '\e363';
+}
+
+.uni-icon-map:before {
+	content: '\e364';
+}
+
+.uni-icon-compose:before {
+	content: '\e400';
+}
+
+.uni-icon-trash:before {
+	content: '\e401';
+}
+
+.uni-icon-upload:before {
+	content: '\e402';
+}
+
+.uni-icon-download:before {
+	content: '\e403';
+}
+
+.uni-icon-close:before {
+	content: '\e404';
+}
+
+.uni-icon-redo:before {
+	content: '\e405';
+}
+
+.uni-icon-undo:before {
+	content: '\e406';
+}
+
+.uni-icon-refresh:before {
+	content: '\e407';
+}
+
+.uni-icon-star:before {
+	content: '\e408';
+}
+
+.uni-icon-plus:before {
+	content: '\e409';
+}
+
+.uni-icon-minus:before {
+	content: '\e410';
+}
+
+.uni-icon-circle:before,
+.uni-icon-checkbox:before {
+	content: '\e411';
+}
+
+.uni-icon-close-filled:before,
+.uni-icon-clear:before {
+	content: '\e434';
+}
+
+.uni-icon-refresh-filled:before {
+	content: '\e437';
+}
+
+.uni-icon-star-filled:before {
+	content: '\e438';
+}
+
+.uni-icon-plus-filled:before {
+	content: '\e439';
+}
+
+.uni-icon-minus-filled:before {
+	content: '\e440';
+}
+
+.uni-icon-circle-filled:before {
+	content: '\e441';
+}
+
+.uni-icon-checkbox-filled:before {
+	content: '\e442';
+}
+
+.uni-icon-closeempty:before {
+	content: '\e460';
+}
+
+.uni-icon-refreshempty:before {
+	content: '\e461';
+}
+
+.uni-icon-reload:before {
+	content: '\e462';
+}
+
+.uni-icon-starhalf:before {
+	content: '\e463';
+}
+
+.uni-icon-spinner:before {
+	content: '\e464';
+}
+
+.uni-icon-spinner-cycle:before {
+	content: '\e465';
+}
+
+.uni-icon-search:before {
+	content: '\e466';
+}
+
+.uni-icon-plusempty:before {
+	content: '\e468';
+}
+
+.uni-icon-forward:before {
+	content: '\e470';
+}
+
+.uni-icon-back:before,
+.uni-icon-left-nav:before {
+	content: '\e471';
+}
+
+.uni-icon-checkmarkempty:before {
+	content: '\e472';
+}
+
+.uni-icon-home:before {
+	content: '\e500';
+}
+
+.uni-icon-navigate:before {
+	content: '\e501';
+}
+
+.uni-icon-gear:before {
+	content: '\e502';
+}
+
+.uni-icon-paperplane:before {
+	content: '\e503';
+}
+
+.uni-icon-info:before {
+	content: '\e504';
+}
+
+.uni-icon-help:before {
+	content: '\e505';
+}
+
+.uni-icon-locked:before {
+	content: '\e506';
+}
+
+.uni-icon-more:before {
+	content: '\e507';
+}
+
+.uni-icon-flag:before {
+	content: '\e508';
+}
+
+.uni-icon-home-filled:before {
+	content: '\e530';
+}
+
+.uni-icon-gear-filled:before {
+	content: '\e532';
+}
+
+.uni-icon-info-filled:before {
+	content: '\e534';
+}
+
+.uni-icon-help-filled:before {
+	content: '\e535';
+}
+
+.uni-icon-more-filled:before {
+	content: '\e537';
+}
+
+.uni-icon-settings:before {
+	content: '\e560';
+}
+
+.uni-icon-list:before {
+	content: '\e562';
+}
+
+.uni-icon-bars:before {
+	content: '\e563';
+}
+
+.uni-icon-loop:before {
+	content: '\e565';
+}
+
+.uni-icon-paperclip:before {
+	content: '\e567';
+}
+
+.uni-icon-eye:before {
+	content: '\e568';
+}
+
+.uni-icon-arrowup:before {
+	content: '\e580';
+}
+
+.uni-icon-arrowdown:before {
+	content: '\e581';
+}
+
+.uni-icon-arrowleft:before {
+	content: '\e582';
+}
+
+.uni-icon-arrowright:before {
+	content: '\e583';
+}
+
+.uni-icon-arrowthinup:before {
+	content: '\e584';
+}
+
+.uni-icon-arrowthindown:before {
+	content: '\e585';
+}
+
+.uni-icon-arrowthinleft:before {
+	content: '\e586';
+}
+
+.uni-icon-arrowthinright:before {
+	content: '\e587';
+}
+
+.uni-icon-pulldown:before {
+	content: '\e588';
+}
+
+.uni-icon-scan:before {
+    content: "\e612";
+}
+
+/* 分界线 */
+.uni-divider{
+    height: 110rpx;
+    display: flex;
+    align-items:center;
+    justify-content: center;
+    position: relative;
+}
+.uni-divider__content{
+    font-size: 28rpx;
+    color: #999;
+    padding: 0 20rpx;
+    position: relative;
+    z-index: 101;
+    background: #F4F5F6;
+}
+.uni-divider__line{
+    background-color: #CCCCCC;
+    height: 1px;
+    width: 100%;
+    position: absolute;
+    z-index: 100;
+    top: 50%;
+    left: 0;
+    transform: translateY(50%);
+}
+
+.left-win-active text{
+	color: #007AFF !important;
+}

+ 773 - 0
components/MediaUploader.vue

@@ -0,0 +1,773 @@
+<template>
+	<view class="media-uploader">
+		<view class="media-grid">
+			<view class="media-item" v-for="(item, index) in mediaList" :key="index">
+				<!-- 图片预览 -->
+				<image v-if="item.type === 'image'" :src="item.url" mode="aspectFill" @tap="previewMedia(index)">
+				</image>
+				<!-- 视频预览 -->
+				<video v-else-if="item.type === 'video'" :id="'video-' + index" :src="item.url" class="video-preview"
+					controls show-center-play-btn enable-play-gesture show-fullscreen-btn object-fit="cover"
+					:initial-time="0" :show-mute-btn="true" :enable-progress-gesture="true" :vslide-gesture="true"
+					:vslide-gesture-in-fullscreen="true" @tap="previewVideo(index)" :poster="item.thumbnail"></video>
+				<!-- 删除按钮 -->
+				<view class="delete-btn" @tap.stop="deleteMedia(index)">
+					<uni-icons type="close" size="12" color="#fff"></uni-icons>
+				</view>
+
+			</view>
+
+			<!-- 上传按钮 -->
+			<view class="upload-trigger" v-if="mediaList.length < maxCount">
+				<view class="upload-box" @tap="chooseMedia">
+					<uni-icons type="camera" size="24" color="#999"></uni-icons>
+					<text class="upload-text">{{mediaList.length}}/{{maxCount}}</text>
+				</view>
+			</view>
+		</view>
+		<text class="upload-tip">最多上传{{maxCount}}个文件</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'MediaUploader',
+		props: {
+			// 最大上传数量
+			maxCount: {
+				type: Number,
+				default: 9
+			},
+			// 已有的媒体列表
+			value: {
+				type: Array,
+				default: () => []
+			},
+			// 上传地址
+			uploadUrl: {
+				type: String,
+				required: true
+			},
+			// 认证token
+			token: {
+				type: String,
+				required: true
+			},
+			// 媒体类型限制
+			mediaType: {
+				type: String,
+				default: 'all', // 可选值:'all'、'image'、'video'
+				validator: function(value) {
+					return ['all', 'image', 'video'].includes(value)
+				}
+			},
+			// 是否使用媒体模式(拍照/相册)
+			media: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				mediaList: []
+			}
+		},
+		watch: {
+			value: {
+				handler(newVal) {
+					if (!Array.isArray(newVal)) {
+						console.warn('MediaUploader: value must be an array');
+						return;
+					}
+
+					this.mediaList = newVal.map(item => {
+						// 如果已经是正确的格式,直接返回
+						if (typeof item === 'object' && item.url && item.type) {
+							return item;
+						}
+
+						// 如果是字符串,转换为对象格式
+						const url = typeof item === 'object' ? item.url : item;
+						return {
+							url: url,
+							type: this.getMediaType(item)
+						}
+					})
+				},
+				immediate: true
+			}
+		},
+		methods: {
+			// 选择媒体文件
+			async chooseMedia() {
+				try {
+					// 检查是否达到最大数量
+					if (this.mediaList.length >= this.maxCount) {
+						uni.showToast({
+							title: `最多只能上传${this.maxCount}个文件`,
+							icon: 'none'
+						});
+						return;
+					}
+
+					// 获取平台信息
+					const platform = uni.getSystemInfoSync().platform;
+					const isH5 = platform === 'h5';
+
+					// 如果是媒体模式
+					if (this.media) {
+						try {
+							const res = await uni.showActionSheet({
+								itemList: ['拍摄', '从相册选择']
+							});
+
+							if (res.tapIndex === 0) {
+								// 拍摄模式
+								if (this.mediaType === 'video') {
+									this.captureVideo();
+								} else {
+									this.captureImage();
+								}
+							} else {
+								// 从相册选择,支持图片和视频
+								this.chooseFromAlbum();
+							}
+							return;
+						} catch (error) {
+							console.error('选择操作失败:', error);
+							return;
+						}
+					}
+
+					// 非媒体模式的原有逻辑
+					if (this.mediaType === 'image') {
+						this.chooseImage();
+					} else if (this.mediaType === 'video') {
+						if (isH5) {
+							this.chooseVideoH5();
+						} else {
+							this.chooseVideoMini();
+						}
+					} else {
+						// 如果是 'all',则显示选择菜单
+						try {
+							const res = await uni.showActionSheet({
+								itemList: ['选择图片', '选择视频']
+							});
+
+							if (res.tapIndex === 0) {
+								this.chooseImage();
+							} else {
+								if (isH5) {
+									this.chooseVideoH5();
+								} else {
+									this.chooseVideoMini();
+								}
+							}
+						} catch (error) {
+							console.error('选择操作失败:', error);
+							return;
+						}
+					}
+				} catch (e) {
+					console.error(e);
+				}
+			},
+
+			// 选择图片
+			async chooseImage() {
+				try {
+					const res = await uni.chooseImage({
+						count: this.maxCount - this.mediaList.length,
+						sizeType: ['original', 'compressed'],
+						sourceType: ['album', 'camera']
+					});
+
+					// 上传图片
+					for (let tempFilePath of res.tempFilePaths) {
+						try {
+							const uploadResult = await this.uploadFile(tempFilePath);
+							const newMedia = {
+								url: uploadResult.data.link,
+								type: 'image'
+							};
+							this.updateMediaList([...this.mediaList, newMedia]);
+						} catch (uploadError) {
+							console.error('图片上传失败:', uploadError);
+							uni.showToast({
+								title: '图片上传失败',
+								icon: 'none'
+							});
+						}
+					}
+				} catch (e) {
+					console.error(e);
+				}
+			},
+
+			// H5平台选择视频
+			async chooseVideoH5() {
+				try {
+					const res = await uni.chooseVideo({
+						sourceType: ['album', 'camera'],
+						compressed: true,
+						camera: 'back'
+					});
+
+					uni.showLoading({
+						title: '处理中...'
+					});
+
+					try {
+						// 上传视频文件
+						const videoResult = await this.uploadFile(res.tempFilePath);
+
+						const newMedia = {
+							url: videoResult.data.link,
+							type: 'video',
+							duration: Math.round(res.duration),
+							// H5暂时不支持视频预览图,可以后续在服务端处理
+							thumbnail: ''
+						};
+
+						this.updateMediaList([...this.mediaList, newMedia]);
+						uni.hideLoading();
+					} catch (error) {
+						uni.hideLoading();
+						uni.showToast({
+							title: '视频处理失败',
+							icon: 'none'
+						});
+						console.error('视频处理失败:', error);
+					}
+				} catch (e) {
+					console.error('选择视频失败:', e);
+					uni.showToast({
+						title: '选择视频失败',
+						icon: 'none'
+					});
+				}
+			},
+
+			// 非H5平台选择视频(小程序和APP)
+			async chooseVideoMini() {
+				try {
+					// #ifdef MP-WEIXIN
+					// 微信小程序使用 chooseMedia
+					wx.chooseMedia({
+						count: 1,
+						mediaType: ['video'],
+						success: async (res) => {
+							const tempFile = res.tempFiles[0];
+							try {
+								uni.showLoading({
+									title: '处理中...'
+								});
+
+								// 上传视频文件
+								const videoResult = await this.uploadFile(tempFile.tempFilePath);
+
+								// 上传视频预览图
+								const thumbResult = await this.uploadFile(tempFile.thumbTempFilePath);
+
+								const newMedia = {
+									url: videoResult.data.link,
+									type: 'video',
+									duration: Math.round(tempFile.duration),
+									thumbnail: thumbResult.data.link
+								};
+
+								this.updateMediaList([...this.mediaList, newMedia]);
+								uni.hideLoading();
+							} catch (error) {
+								uni.hideLoading();
+								uni.showToast({
+									title: '视频处理失败',
+									icon: 'none'
+								});
+								console.error('视频处理失败:', error);
+							}
+						},
+						fail: (err) => {
+							console.error('选择视频失败:', err);
+							uni.showToast({
+								title: '选择视频失败',
+								icon: 'none'
+							});
+						}
+					});
+					// #endif
+
+					// #ifdef APP-PLUS
+					// APP端使用 chooseVideo
+					uni.chooseVideo({
+						sourceType: ['camera', 'album'],
+						compressed: true,
+						success: async (res) => {
+							try {
+								uni.showLoading({
+									title: '处理中...'
+								});
+
+								// 上传视频文件
+								const videoResult = await this.uploadFile(res.tempFilePath);
+
+								// 获取视频缩略图
+								const thumbPath = await this.getVideoThumb(res.tempFilePath);
+								let thumbnail = '';
+
+								if (thumbPath) {
+									try {
+										const thumbResult = await this.uploadFile(thumbPath);
+										thumbnail = thumbResult.data.link;
+									} catch (error) {
+										console.error('缩略图上传失败:', error);
+									}
+								}
+
+								const newMedia = {
+									url: videoResult.data.link,
+									type: 'video',
+									duration: Math.round(res.duration || 0),
+									thumbnail: thumbnail || videoResult.data
+										.link // 如果没有缩略图,使用视频地址作为预览图
+								};
+
+								this.updateMediaList([...this.mediaList, newMedia]);
+								uni.hideLoading();
+							} catch (error) {
+								uni.hideLoading();
+								uni.showToast({
+									title: '视频处理失败',
+									icon: 'none'
+								});
+								console.error('视频处理失败:', error);
+							}
+						},
+						fail: (err) => {
+							console.error('选择视频失败:', err);
+							uni.showToast({
+								title: '选择视频失败',
+								icon: 'none'
+							});
+						}
+					});
+					// #endif
+
+					// #ifdef H5
+					// 如果在H5环境下不小心调用了这个方法,就调用H5的方法
+					this.chooseVideoH5();
+					// #endif
+				} catch (e) {
+					console.error('选择视频失败:', e);
+					uni.showToast({
+						title: '选择视频失败',
+						icon: 'none'
+					});
+				}
+			},
+
+			// 拍摄视频
+			async captureVideo() {
+				try {
+					const res = await uni.chooseVideo({
+						sourceType: ['camera'],
+						compressed: true,
+						camera: 'back'
+					});
+
+					uni.showLoading({
+						title: '处理中...'
+					});
+
+					try {
+						// 上传视频文件
+						const videoResult = await this.uploadFile(res.tempFilePath);
+
+						const newMedia = {
+							url: videoResult.data.link,
+							type: 'video',
+							duration: Math.round(res.duration),
+							thumbnail: ''
+						};
+
+						this.updateMediaList([...this.mediaList, newMedia]);
+						uni.hideLoading();
+					} catch (error) {
+						uni.hideLoading();
+						uni.showToast({
+							title: '视频处理失败',
+							icon: 'none'
+						});
+						console.error('视频处理失败:', error);
+					}
+				} catch (e) {
+					console.error('拍摄视频失败:', e);
+					uni.showToast({
+						title: '拍摄视频失败',
+						icon: 'none'
+					});
+				}
+			},
+
+			// 拍摄图片
+			async captureImage() {
+				try {
+					const res = await uni.chooseImage({
+						count: 1,
+						sizeType: ['original', 'compressed'],
+						sourceType: ['camera']
+					});
+
+					// 上传图片
+					for (let tempFilePath of res.tempFilePaths) {
+						try {
+							const uploadResult = await this.uploadFile(tempFilePath);
+							const newMedia = {
+								url: uploadResult.data.link,
+								type: 'image'
+							};
+							this.updateMediaList([...this.mediaList, newMedia]);
+						} catch (uploadError) {
+							console.error('图片上传失败:', uploadError);
+							uni.showToast({
+								title: '图片上传失败',
+								icon: 'none'
+							});
+						}
+					}
+				} catch (e) {
+					console.error(e);
+				}
+			},
+
+			// 从相册选择(支持图片和视频)
+			async chooseFromAlbum() {
+				// #ifdef MP-WEIXIN
+				try {
+					const res = await new Promise((resolve, reject) => {
+						wx.chooseMedia({
+							count: this.maxCount - this.mediaList.length,
+							mediaType: ['image', 'video'],
+							sourceType: ['album'],
+							success: resolve,
+							fail: reject
+						});
+					});
+
+					for (let tempFile of res.tempFiles) {
+						try {
+							uni.showLoading({
+								title: '处理中...'
+							});
+
+							// 上传文件
+							const fileResult = await this.uploadFile(tempFile.tempFilePath);
+
+							if (tempFile.fileType === 'video') {
+								// 处理视频
+								const thumbPath = tempFile.thumbTempFilePath;
+								let thumbnail = '';
+								if (thumbPath) {
+									try {
+										const thumbResult = await this.uploadFile(thumbPath);
+										thumbnail = thumbResult.data.link;
+									} catch (error) {
+										console.error('缩略图上传失败:', error);
+									}
+								}
+
+								const newMedia = {
+									url: fileResult.data.link,
+									type: 'video',
+									duration: Math.round(tempFile.duration || 0),
+									thumbnail: thumbnail || fileResult.data.link
+								};
+								this.updateMediaList([...this.mediaList, newMedia]);
+							} else {
+								// 处理图片
+								const newMedia = {
+									url: fileResult.data.link,
+									type: 'image'
+								};
+								this.updateMediaList([...this.mediaList, newMedia]);
+							}
+
+							uni.hideLoading();
+						} catch (error) {
+							uni.hideLoading();
+							uni.showToast({
+								title: '上传失败',
+								icon: 'none'
+							});
+							console.error('上传失败:', error);
+						}
+					}
+				} catch (e) {
+					console.error('选择失败:', e);
+					uni.showToast({
+						title: '选择失败',
+						icon: 'none'
+					});
+				}
+				// #endif
+
+				// #ifdef APP-PLUS || H5
+				// APP和H5使用系统相册
+				try {
+					const res = await uni.chooseImage({
+						count: this.maxCount - this.mediaList.length,
+						sizeType: ['original', 'compressed'],
+						sourceType: ['album']
+					});
+
+					// 上传图片
+					for (let tempFilePath of res.tempFilePaths) {
+						try {
+							const uploadResult = await this.uploadFile(tempFilePath);
+							const newMedia = {
+								url: uploadResult.data.link,
+								type: 'image'
+							};
+							this.updateMediaList([...this.mediaList, newMedia]);
+						} catch (uploadError) {
+							console.error('上传失败:', uploadError);
+							uni.showToast({
+								title: '上传失败',
+								icon: 'none'
+							});
+						}
+					}
+				} catch (e) {
+					console.error(e);
+				}
+				// #endif
+			},
+
+			async getVideoThumb(videoPath) {
+				return new Promise((resolve, reject) => {
+					try {
+						// #ifdef APP-PLUS
+						// 先尝试使用 plus.io.getVideoInfo
+						plus.io.getVideoInfo({
+							filePath: videoPath,
+							success: (info) => {
+								if (info.thumbnail) {
+									resolve(info.thumbnail);
+								} else {
+									// 如果获取不到缩略图,尝试使用 uni.getVideoInfo
+									this.getVideoThumbUnified(videoPath).then(resolve).catch(() =>
+										resolve(''));
+								}
+							},
+							error: () => {
+								// 如果 plus.io.getVideoInfo 失败,尝试使用 uni.getVideoInfo
+								this.getVideoThumbUnified(videoPath).then(resolve).catch(() =>
+									resolve(''));
+							}
+						});
+						// #endif
+
+						// #ifndef APP-PLUS
+						// 非APP平台使用 uni.getVideoInfo
+						this.getVideoThumbUnified(videoPath).then(resolve).catch(() => resolve(''));
+						// #endif
+					} catch (e) {
+						console.error('获取视频缩略图失败:', e);
+						resolve('');
+					}
+				});
+			},
+
+			// 使用 uni.getVideoInfo 获取视频缩略图
+			async getVideoThumbUnified(videoPath) {
+				return new Promise((resolve, reject) => {
+					uni.getVideoInfo({
+						src: videoPath,
+						success: (res) => {
+							if (res.thumbTempFilePath) {
+								resolve(res.thumbTempFilePath);
+							} else {
+								reject(new Error('No thumbnail'));
+							}
+						},
+						fail: reject
+					});
+				});
+			},
+
+			// 文件上传方法
+			async uploadFile(filePath) {
+				uni.showLoading({
+					title: '上传中...',
+					mask: true
+				})
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: this.uploadUrl,
+						filePath: filePath,
+						header: {
+							"Blade-Auth": this.token
+						},
+						name: 'file',
+						success: (res) => {
+							resolve(JSON.parse(res.data));
+							uni.hideLoading()
+						},
+						fail: reject
+					});
+				});
+			},
+
+			// 删除媒体
+			deleteMedia(index) {
+				const newList = [...this.mediaList];
+				newList.splice(index, 1);
+				this.updateMediaList(newList);
+			},
+
+			// 预览媒体
+			previewMedia(index) {
+				const item = this.mediaList[index];
+				if (item.type === 'image') {
+					const imageUrls = this.mediaList
+						.filter(item => item.type === 'image')
+						.map(item => item.url);
+					const currentIndex = imageUrls.indexOf(item.url);
+
+					uni.previewImage({
+						urls: imageUrls,
+						current: currentIndex
+					});
+				}
+			},
+
+			// 预览视频
+			previewVideo(index) {
+				const item = this.mediaList[index];
+				if (item.type === 'video') {
+					// H5环境下不需要特殊处理,让视频组件自己处理全屏
+					// #ifdef MP-WEIXIN
+					const videoContext = uni.createVideoContext(`video-${index}`, this);
+					if (videoContext) {
+						videoContext.requestFullScreen();
+					}
+					// #endif
+				}
+			},
+
+			// 更新媒体列表
+			updateMediaList(list) {
+				this.mediaList = list;
+				this.$emit('input', list.map(item => item.url));
+				this.$emit('change', list);
+			},
+
+			// 获取媒体类型
+			getMediaType(url) {
+				// 如果已经是对象格式,直接返回其类型
+				if (typeof url === 'object' && url.type) {
+					return url.type;
+				}
+
+				// 如果是字符串,则通过扩展名判断
+				if (typeof url === 'string') {
+					const videoExts = ['.mp4', '.mov', '.avi', '.wmv'];
+					const ext = url.substring(url.lastIndexOf('.')).toLowerCase();
+					return videoExts.includes(ext) ? 'video' : 'image';
+				}
+
+				// 默认返回图片类型
+				return 'image';
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.media-uploader {
+		padding: 15px;
+
+		.media-grid {
+			display: flex;
+			flex-wrap: wrap;
+			gap: 10px;
+		}
+
+		.media-item {
+			position: relative;
+			width: calc((100% - 20px) / 3);
+			height: 0;
+			padding-bottom: calc((100% - 20px) / 3);
+			background-color: #f8f8f8;
+			border-radius: 8px;
+			overflow: hidden;
+
+			image,
+			video {
+				position: absolute;
+				width: 100%;
+				height: 100%;
+				object-fit: cover;
+			}
+
+			.delete-btn {
+				position: absolute;
+				top: 5px;
+				right: 5px;
+				width: 20px;
+				height: 20px;
+				background: rgba(0, 0, 0, 0.5);
+				border-radius: 50%;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				z-index: 2;
+			}
+
+			.media-mask {
+				position: absolute;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				height: 30px;
+				background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				z-index: 1;
+			}
+		}
+
+		.upload-trigger {
+			width: calc((100% - 20px) / 3);
+			height: 0;
+			padding-bottom: calc((100% - 20px) / 3);
+			position: relative;
+
+			.upload-box {
+				position: absolute;
+				width: 100%;
+				height: 100%;
+				background-color: #f8f8f8;
+				border-radius: 8px;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+
+				.upload-text {
+					margin-top: 5px;
+					font-size: 12px;
+					color: #999;
+				}
+			}
+		}
+
+		.upload-tip {
+			margin-top: 10px;
+			font-size: 12px;
+			color: #999;
+		}
+	}
+</style>

+ 50 - 0
components/custom-navbar/custom-navbar.vue

@@ -0,0 +1,50 @@
+<template>
+	<view class="custom-navbar">
+		<!-- 状态栏占位 -->
+		<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
+		<!-- 导航栏主体 -->
+		<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'custom-navbar',
+		data() {
+			return {
+				statusBarHeight: 0,
+				navBarHeight: 44, // 导航栏高度,单位px
+			}
+		},
+		created() {
+			// 获取状态栏高度
+			const systemInfo = uni.getSystemInfoSync()
+			this.statusBarHeight = systemInfo.statusBarHeight
+		}
+	}
+</script>
+
+<style>
+	.custom-navbar {
+		width: 100%;
+		position: fixed;
+		top: 0;
+		left: 0;
+		z-index: 999;
+		background-color: transparent;
+	}
+	
+	.status-bar {
+		width: 100%;
+		background-color: transparent;
+	}
+	
+	.navbar-content {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		background-color: transparent;
+	}
+</style> 

+ 20 - 0
components/progress.vue

@@ -0,0 +1,20 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"progress",
+		data() {
+			return {
+				
+			};
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 27 - 0
components/statusBar.vue

@@ -0,0 +1,27 @@
+<template>
+	<view>
+		<!-- //状态栏 -->
+		<u-navbar leftIconSize="0" bgColor="transparent" :titleStyle="{color:'#2e3235'}">
+		</u-navbar>
+		<view class="status_bar bg-transparent">
+			<view class="top_view"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 233 - 0
components/u-city-select.vue

@@ -0,0 +1,233 @@
+<template>
+	<u-popup :show="value" mode="bottom" round="15" :closeable="true" :zIndex="uZIndex" @close="close">
+		<u-tabs v-if="value" :list="genTabsList" :current="tabsIndex" @change="tabsChange" ref="tabs"></u-tabs>
+		<view class="area-box">
+			<view class="flex-row-plus" :class="{ 'change':isChange }">
+				<view class="area-item">
+					<view class="pad-lr-10 u-bg-gray" style="height: 100%;">
+						<scroll-view :scroll-y="true" style="height: 100%">
+							<u-cell-group>
+								<u-cell v-for="(item, index) in provinces" :title="item.label" :arrow="false"
+									:key="index" @click="provinceChange(index)">
+									<u-icon v-if="isChooseP&&province===index" slot="right-icon" size="34rpx"
+										name="checkbox-mark"></u-icon>
+								</u-cell>
+							</u-cell-group>
+						</scroll-view>
+					</view>
+				</view>
+				<view class="area-item">
+					<view class="pad-lr-10 u-bg-gray" style="height: 100%;">
+						<scroll-view :scroll-y="true" style="height: 100%">
+							<u-cell-group v-if="isChooseP">
+								<u-cell v-for="(item, index) in citys" :title="item.label" :arrow="false" :key="index"
+									@click="cityChange(index)">
+									<u-icon v-if="isChooseC&&city===index" slot="right-icon" size="34rpx"
+										name="checkbox-mark"></u-icon>
+								</u-cell>
+							</u-cell-group>
+						</scroll-view>
+					</view>
+				</view>
+
+				<view class="area-item">
+					<view class="pad-lr-10 u-bg-gray" style="height: 100%;">
+						<scroll-view :scroll-y="true" style="height: 100%">
+							<u-cell-group v-if="isChooseC">
+								<u-cell v-for="(item, index) in areas" :title="item.label" :arrow="false" :key="index"
+									@click="areaChange(index)">
+									<u-icon v-if="isChooseA&&area===index" slot="right-icon" size="34rpx"
+										name="checkbox-mark"></u-icon>
+								</u-cell>
+							</u-cell-group>
+						</scroll-view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	import provinces from "../static/js/province.js";
+	import citys from "../static/js/city.js";
+	import areas from "../static/js/area.js";
+	/**
+	 * city-select 省市区级联选择器
+	 * @property {String Number} z-index 弹出时的z-index值(默认1075)
+	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
+	 * @property {String} default-region 默认选中的地区,中文形式
+	 * @property {String} default-code 默认选中的地区,编号形式
+	 */
+	export default {
+		name: 'u-city-select',
+		props: {
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"]
+			defaultRegion: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			// 默认显示地区的编码,defaultRegion和areaCode同时存在,areaCode优先,可传类似["13", "1303", "130304"]
+			areaCode: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			// 是否允许通过点击遮罩关闭Picker
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			}
+		},
+		data() {
+			return {
+				cityValue: "",
+				isChooseP: false, //是否已经选择了省
+				province: 0, //省级下标
+				provinces: provinces,
+				isChooseC: false, //是否已经选择了市
+				city: 0, //市级下标
+				citys: citys[0],
+				isChooseA: false, //是否已经选择了区
+				area: 0, //区级下标
+				areas: areas[0][0],
+				tabsIndex: 0,
+			}
+		},
+		mounted() {
+			this.init();
+		},
+		computed: {
+			isChange() {
+				return this.tabsIndex > 1;
+			},
+			genTabsList() {
+				let tabsList = [{
+					name: "请选择"
+				}];
+				if (this.isChooseP) {
+					tabsList[0]['name'] = this.provinces[this.province]['label'];
+					tabsList[1] = {
+						name: "请选择"
+					};
+				}
+				if (this.isChooseC) {
+					tabsList[1]['name'] = this.citys[this.city]['label'];
+					tabsList[2] = {
+						name: "请选择"
+					};
+				}
+				if (this.isChooseA) {
+					tabsList[2]['name'] = this.areas[this.area]['label'];
+				}
+				return tabsList;
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		methods: {
+			init() {
+				if (this.areaCode.length == 3) {
+					this.setProvince("", this.areaCode[0]);
+					this.setCity("", this.areaCode[1]);
+					this.setArea("", this.areaCode[2]);
+				} else if (this.defaultRegion.length == 3) {
+					this.setProvince(this.defaultRegion[0], "");
+					this.setCity(this.defaultRegion[1], "");
+					this.setArea(this.defaultRegion[2], "");
+				};
+			},
+			setProvince(label = "", value = "") {
+				this.provinces.map((v, k) => {
+					if (value ? v.value == value : v.label == label) {
+						this.provinceChange(k);
+					}
+				})
+			},
+			setCity(label = "", value = "") {
+				this.citys.map((v, k) => {
+					if (value ? v.value == value : v.label == label) {
+						this.cityChange(k);
+					}
+				})
+			},
+			setArea(label = "", value = "") {
+				this.areas.map((v, k) => {
+					if (value ? v.value == value : v.label == label) {
+						this.isChooseA = true;
+						this.area = k;
+					}
+				})
+			},
+			close() {
+				this.$emit('input', false);
+			},
+			tabsChange(e) {
+				this.tabsIndex = e.index;
+			},
+			provinceChange(index) {
+				this.isChooseP = true;
+				this.isChooseC = false;
+				this.isChooseA = false;
+				this.province = index;
+				this.citys = citys[index];
+				this.tabsIndex = 1;
+			},
+			cityChange(index) {
+				this.isChooseC = true;
+				this.isChooseA = false;
+				this.city = index;
+				this.areas = areas[this.province][index];
+				this.tabsIndex = 2;
+			},
+			areaChange(index) {
+				this.isChooseA = true;
+				this.area = index;
+				let result = {};
+				result.province = this.provinces[this.province];
+				result.city = this.citys[this.city];
+				result.area = this.areas[this.area];
+				this.$emit('city-change', result);
+				this.close();
+			}
+		}
+
+	}
+</script>
+<style lang="scss">
+	.area-box {
+		width: 100%;
+		overflow: hidden;
+		height: 800rpx;
+
+		>view {
+			width: 150%;
+			transition: transform 0.3s ease-in-out 0s;
+			transform: translateX(0);
+
+			&.change {
+				transform: translateX(-33.3333333%);
+			}
+		}
+
+		.area-item {
+			width: 33.3333333%;
+			height: 800rpx;
+		}
+	}
+</style>

+ 429 - 0
config/api.js

@@ -0,0 +1,429 @@
+import {
+	http,
+	toast
+} from '@/uni_modules/uview-plus'
+
+let prefix = '';
+// #ifdef H5
+prefix = '/api'
+// #endif
+// #ifndef H5
+prefix = ''
+// #endif
+
+/**
+ * 聊天
+ */
+
+
+export const getChatHistory = (data) => http.get(prefix + '/blade_chat/customer-service/chat-history?userId=' + data
+	.userId + '&asc=' + data.asc + '&current=' + data.current + '&size=' + data.size) //获取聊天历史
+export const unreadCount = (data) => http.get(prefix + '/blade_chat/user/unread-count?userId=' + data
+	.userId) //获取搜索历史
+export const markMessagesRead = (data) => http.post(prefix + '/blade_chat/user/mark-messages-as-read?userId=' + data
+	.userId) //标记消息已读
+export const getTopQuestions = () => http.get(prefix + '/blade_msg_template/getTopQuestions') //获取顶级问题列表
+export const getQuestionResponse = (id) => http.get(prefix + '/blade_msg_template/getQuestionResponse?id=' +
+	id) //获取问题回复或子问题列表
+/**
+ * 搜索历史相关
+ */
+export const getHistorySearchList = (data) => http.get(prefix + '/blade-historySearch/getList?type=' + data
+	.type) //获取搜索历史
+export const submitHistorySearch = (params) => http.post(prefix + '/blade-historySearch/submit', params) //增加搜索历史
+export const delHistorySearch = () => http.post(prefix + '/blade-historySearch/remove') //删除搜索历史
+
+/**
+ * 提交反馈
+ */
+export const feedbackList = (data) => http.get(prefix + '/blade-feedback/page?current=' + data.current +
+	'&size=' + data.size + '&type=' + data.type)
+export const getFeedback = (data) => http.get(prefix + '/blade-feedback/getFeedback')
+export const feedbackSave = (params) => http.post(prefix + '//blade-feedback/save', params) // 意见反馈
+/**
+ * 购物车
+ */
+export const productCartSave = (params) => http.post(prefix + '/blade-cart/submit', params) //加入/修改购物车
+export const productCartCount = (params) => http.get(prefix + '/blade-cart/count', params) //获取购物车数量
+export const productCartList = (data) => http.get(prefix + '/blade-cart/list?current=' + data.current +
+	'&size=' + data.size) //获取购物车所有商品
+export const productCartRemoveIds = (ids) => http.post(prefix + '/blade-cart/remove?ids=' + ids) //删除购物车对应商品
+export const productCartTotalPrice = () => http.get(prefix + '/blade-cart/totalPrice') //获取购物车总价
+export const saveCartOrder = (params) => http.post(prefix + '/blade-order/saveCart', params) //购物车批量下单
+/**
+ * 订单
+ */
+export const getOrderStatusCounts = () => http.get(prefix + '/blade-order/status-counts') // 获取各订单状态的数量
+export const queryExpress = (data) => http.post(prefix + '/blade-order/queryExpress?number=' + data.number + (data
+	.mobile ? '&mobile=' + data.mobile : '')) //查询物流信息
+export const orderAfterDetail = (data) => http.get(prefix + '/blade-order/after/detail?orderId=' + data.orderId) //售后详情
+export const orderAfterDetailById = (data) => http.get(prefix + '/blade-order/after/detailById?id=' + data.id) //售后详情
+export const submitOrder = (data) => http.post(prefix + '/blade-order/save', data) // 保存订单
+export const submitOrderAfter = (data) => http.post(prefix + '/blade-order/after/save', data) // 申请售后
+export const saveScore = (data) => http.post(prefix + '/blade-score/score/save', data) // 提交评价
+export const confirmReceipt = (data) => http.post(prefix + '/blade-order/confirmReceipt', data) // 确认收货
+export const cancelOrder = (data) => http.post(prefix + '/blade-order/cancel', data) // 取消订单
+export const cancelAfter = (data) => http.post(prefix + '/blade-order/after/cancel', data) // 取消售后
+export const updateExpressCode = (data) => http.post(prefix + '/blade-order/after/updateExpressCode', data) // 填写单号
+export const getOrderList = (data) => http.get(prefix + '/blade-order/page?current=' + data.current +
+	'&size=' + data.size + '&status=' + data.status + '&orderCode=' + data.orderCode + '&isAfter=' + data.isAfter +
+	'&isEvaluate=' + data.isEvaluate
+) //获取订单列表
+export const getAfterList = (data) => http.get(prefix + '/blade-order/after/page?current=' + data.current +
+	'&size=' + data.size + '&orderId=' + data.orderId) //售后记录
+export const orderDetail = (data) => http.get(prefix + '/blade-order/detail', {
+	params: {
+		id: data.id
+	}
+});
+export const getOrderPayDetail = (orderId) => http.get(prefix + '/blade-order/payDetail?orderId=' + orderId);
+
+export const countPaidProducts = () => http.get(prefix + '/blade-order/countPaidProducts');
+export const getMemberOrderStatistics = (data) => http.get(prefix + '/blade-order/memberStatistics', {
+	params: data
+}); //获取指定用户的订单统计信息
+export const getMemberOrderList = (data) => http.get(prefix + '/blade-order/memberOrderList', {
+	params: data
+}); //获取指定用户的订单列表
+export const getMemberlazyList = (data) => http.post(prefix + '/blade-member/member/lazyList',
+	data); //获取会员团队
+export const teamStatistics = (data) => http.get(prefix + '/blade-member/member/teamStatistics',
+	data); //获取团队统计
+/**
+ * 收货地址
+ */
+export const updateAddress = (data) => http.post(prefix + '/blade-order/updateAddress', data) // 更改收货地址
+export const merchantAddressDetail = () => http.get(prefix + '/blade-merchantAddress/detail') //商家退货地址
+export const addressList = (data) => http.get(prefix + '/blade-address/getList') //收货地址
+export const getAddress = () => http.get(prefix + '/blade-address/getAddress') //获取客户地址
+export const addressDetail = (data) => http.get(prefix + '/blade-address/detail?id=' + data.id) //获取默认地址
+export const addressDel = (data) => http.post(prefix + '/blade-address/remove?ids=' + data.ids) // 删除地址
+export const addressSubmit = (data) => http.post(prefix + '/blade-address/submit', data) // 新增/修改地址
+
+/**
+ * 商城相关
+ */
+export const scoreList = (data) => http.get(prefix + '/blade-score/score/list?current=' + data.current +
+	'&size=' + data.size + '&productId=' + data.productId + '&images=' + data.images) //评论
+export const productClearCollect = (params) => http.post(prefix + '/blade-product/product/clearCollect',
+	params) // 产品取消收藏
+export const productAddCollect = (params) => http.post(prefix + '/blade-product/product/addCollect',
+	params) // 产品收藏
+export const productCollectList = (data) => http.get(prefix + '/blade-product/product/collectList', {
+	params: data
+}) // 收藏列表
+
+export const productDetail = (data) => http.get(prefix + '/blade-product/product/detail?id=' + data.id) //产品详情
+
+export const productList = (data) => {
+	// 手动构建查询参数,过滤空值
+	const params = [];
+	if (data.current) params.push(`current=${encodeURIComponent(data.current)}`);
+	if (data.size) params.push(`size=${encodeURIComponent(data.size)}`);
+	if (data.categoriesId) params.push(`categoriesId=${encodeURIComponent(data.categoriesId)}`);
+	if (data.name) params.push(`name=${encodeURIComponent(data.name)}`);
+	if (data.sort) params.push(`sort=${encodeURIComponent(data.sort)}`);
+	if (data.arrow) params.push(`arrow=${encodeURIComponent(data.arrow)}`);
+
+	const queryString = params.length > 0 ? params.join('&') : '';
+	const url = prefix + '/blade-product/product/list' + (queryString ? '?' + queryString : '');
+	return http.get(url);
+}
+
+// 商品搜索接口
+export const productSearch = (data) => http.post(prefix + '/blade-product/product/productSearch', data)
+
+export const getShopIndexList = () => http.get(prefix + '/blade-product/product/getIndexList') // 获取商品首页列表
+export const getNewProducts = (data) => http.get(prefix + '/blade-product/product/getNewProducts', {
+	params: {
+		current: data.current || 1,
+		size: data.size || 10,
+		name: data.name || '',
+		sort: data.sort || '',
+		arrow: data.arrow || ''
+	}
+}) // 获取新品上市商品列表
+
+// 获取代理商品列表(不分页)
+export const getAgentProducts = (data) => http.get(prefix + '/blade-product/product/getAgentProducts', {
+	params: {
+		name: data?.name || ''
+	}
+})
+
+// 获取精选唐卡商品列表(分页)
+export const getSelectedProducts = (data) => http.get(prefix + '/blade-product/product/getSelectedProducts', {
+	params: {
+		current: data.current || 1,
+		size: data.size || 6,
+		name: data?.name || ''
+	}
+})
+
+export const categoriesList = () => http.get(prefix + '/blade-categories/categories/getList') // 获取商品分类
+
+/**
+ * 家庭相关
+ */
+export const getFamilyList = (data) => http.get(prefix + '/blade-family/family/getList', data) // 获取当前用户全部家庭
+export const getFamilyPetList = (data) => http.post(prefix + '/blade-family/family/getFamilyPetList',
+	data) // 获取当前用户全部家庭的宠物
+export const saveFamily = (data) => http.post(prefix + '/blade-family/family/save', data) // 新建家庭
+export const updateFamily = (data) => http.post(prefix + '/blade-family/family/update', data) // 更新家庭
+export const getFamilyDetail = (id) => http.get(prefix + '/blade-family/family/getFamilyDetail?id=' +
+	id) // 获取家庭详情
+export const getFamilyMemberList = (id) => http.get(prefix + '/blade-family/family/getFamilyMemberList?id=' +
+	id) // 获取家庭成员
+export const dissolveFamily = (data) => http.post(prefix + '/blade-family/family/dissolveFamily', data) // 解散家庭
+export const familyDetailById = (id) => http.get(prefix + '/blade-family/family/detailById?id=' +
+	id) // 根据ID获取家庭详情
+
+/**
+ * 宠物相关
+ */
+export const getPetDetail = (id) => http.get(prefix + '/blade-memberPet/memberPet/detail?id=' +
+	id) // 根据ID获取宠物信息
+export const memberPetList = (data) => http.post(prefix + '/blade-memberPet/memberPet/getList', data) // 获取所有宠物
+export const saveMemberPet = (data) => http.post(prefix + '/blade-memberPet/memberPet/save', data) // 保存宠物
+export const updateMemberPet = (data) => http.post(prefix + '/blade-memberPet/memberPet/update', data) // 更新宠物信息
+export const getBreedList = (data) => http.post(prefix + '/blade-breed/breed/getList', data) // 获取品种
+
+/**
+ * 
+ *  登录相关
+ */
+export const register = (params) => http.post(prefix + '/blade-auth/oauth/register', params) // 注册
+export const login = (params) => http.post(prefix + '/blade-auth/oauth/tokenAPP', params) // 登录
+export const logout = () => http.get(prefix + '/blade-auth/oauth/logout', ) // 退出登录
+export const userLogin = (params) => http.post(prefix +
+	'/blade-auth/oauth/tokenWeb', params
+) // 登录、刷新token
+export const getCode = (params) => http.post(prefix + '/blade-auth/oauth/email', params) // 获取邮箱验证码
+export const updatePassword = (params) => http.post(prefix + '/blade-member/member/updatePassword',
+	params) // 修改密码
+export const forgetPassword = (params) => http.post(prefix + '/blade-auth/forgetPassword', params) //忘记密码
+/**
+ * 用户相关
+ */
+export const memberUpdatePayPassword = (params) => http.post(prefix + '/blade-member/member/updatePayPassword',
+	params) //修改交易密码
+/**
+ *  行情相关
+ */
+export const comlstkm = (params) => http.post(prefix + '/blade-quotes/quotesIndex/comlstkm',
+	params) // K线-获取最新动态柱旧版
+export const comlstkmv2 = (params) => http.post(prefix + '/blade-quotes/quotesIndex/comlstkmv2',
+	params) // K线-获取最新柱v2
+export const quotesList = (params) => http.post(prefix + '/blade-quotes/quotesIndex/quotesList',
+	params) // 首页行情数据
+export const quotesIndexSearch = (params) => http.post(prefix + '/blade-quotes/quotesIndex/search',
+	params) // 首页行情数据
+export const historyKData = (params) => http.post(prefix + '/blade-quotes/quotesIndex/comkm4v2', params) // 历史K线
+export const quotesDetail = (params) => http.post(prefix + '/blade-quotes/quotesIndex/get', params) // 历史K线
+export const optionalPage = (params) => http.post(prefix + '/blade-optional/optional/page', params) // 自选列表
+export const optionalAdd = (params) => http.post(prefix + '/blade-optional/optional/add', params) // 添加自选
+export const optionalCancel = (params) => http.post(prefix + '/blade-optional/optional/cancel', params) // 取消自选
+export const optionalDetail = (params) => http.post(prefix + '/blade-optional/optional/getDetail',
+	params) // 自选信息
+/**
+ * 理财相关
+ */
+export const queryInvestAll = () => http.get(prefix + '/blade_invest/invest/queryInvestAll') // 理财页面详情
+export const queryInvestDetail = (data) => http.get(prefix + '/blade_invest/invest/detail?id=' + data) // 理财页面详情
+export const memberInvestBuy = (data) => http.post(prefix + '/blade-memberInvest/memberInvest/buy', data) // 买入
+export const memberInvestGetById = (data) => http.post(prefix + '/blade-memberInvest/memberInvest/getById',
+	data) // 查询详情
+export const incomeSelectJanuary = (data) => http.post(prefix + '/blade_income/income/selectJanuary',
+	data) // 走势图记录
+export const incomeList = (data) => http.get(prefix + '/blade_income/income/list', data) // 走势图记录分页
+export const investList = (data) => http.post(prefix + '/blade-memberInvest/memberInvest/investList',
+	data) // 买入记录
+export const investDetail = (data) => http.post(prefix + '/blade-memberInvest/memberInvest/detail',
+	data) // 买入详情
+export const statisticsMyBuyOrder = () => http.get(prefix +
+	'/blade_investment_order/investmentorder/statisticsMyBuyOrder') // 理财详情
+/**
+ * 投资
+ */
+export const investmentList = (data) => http.get(prefix + '/blade_investment/investment/list?current=' + data
+	.current +
+	'&size=' + data.size + '&investTypeId=' + data.investTypeId + '&status=' + data.status) // 理财页面详情
+export const investmentDetail = (data) => http.get(prefix + '/blade_investment/investment/detail?id=' +
+	data) // 买入详情
+export const investmentPayDetail = (data) => http.post(prefix +
+	'/blade_investment_order/investmentorder/payDetail',
+	data) // 购买成功详情
+export const investmentBuy = (data) => http.post(prefix + '/blade_investment_order/investmentorder/buy',
+	data) // 购买成功详情
+export const investmentOrderDetail = (data) => http.get(prefix +
+	'/blade_investment_order/investmentorder/detail?id=' +
+	data) // 购买成功详情
+export const investmentStatistics = (data) => http.post(prefix +
+	'/blade_investment_order/investmentorder/statistics',
+	data) // 购买成功详情
+export const investmentSelectMyPage = (data) => http.post(prefix +
+	'/blade_investment_order/investmentorder/selectMyPage', data) // 购买成功详情
+export const investmentSelectMyLogs = (data) => http.post(prefix +
+	'/blade_investment_order/investmentorder/selectMyLogs', data) // 购买成功详情
+export const investmentSelectMyIncomePage = (data) => http.post(prefix +
+	'/blade_investment_order_income/investmentorderincome/selectMyIncomePage', data) // 购买成功详情
+/**
+ * 用户等级
+ */
+export const memberLevelDetail = (data) => http.get(prefix + '/blade_member_level/memberlevel/detail?id=' +
+	data) // 购买成功详情
+/**
+ * 钱包
+ */
+export const memberWalletDetail = () => http.get(prefix + '/blade-memberWallet/memberWallet/detail') // 自选信息
+export const getBalance = (data) => http.get(prefix + '/blade-memberWallet/memberWallet/getBalance?type=' +
+	data) // 获取用户余额
+export const getBalanceDetail = (data) => http.get(prefix + '/blade-memberWallet/memberWallet/detail?type=' +
+	data) // 获取用户余额
+export const qrCodeAddress = () => http.get(prefix + '/blade-address/address/list') // 充币地址和充币二维码
+export const withdrawalSave = (data) => http.post(prefix + '/blade-withdrawal/memberwithdrawal/save',
+	data) // 提币功能确认转出
+export const chargeMoneyRecord = (data) => http.post(prefix + '/blade-member/member/assets/log/charge/page',
+	data) // 充币记录
+export const withdrawalRecord = (data) => http.post(prefix + '/blade-withdrawal/memberwithdrawal/log/list',
+	data) // 提币记录
+export const memberWalletExchange = (params) => http.post(prefix + '/blade-memberWallet/memberWallet/exchange',
+	params) // 钱包兑换
+export const walletWebDetail = () => http.get(prefix +
+	'/blade-memberWallet/memberWallet/walletWebDetail') // 钱包兑换
+export const walletWebRecharge = (data) => http.post(prefix + '/blade-member/member/assets/log/recharge',
+	data) // 钱包兑换
+
+/**
+ * 余额宝
+ */
+export const memberprofitlogmemberProfitLogList = (data) => http.post(prefix +
+	'/blade_member_profit_log/memberprofitlog/memberProfitLogList', data) // 钱包兑换
+export const memberprofitlogchange = (data) => http.post(prefix +
+	'/blade_member_profit_log/memberprofitlog/change',
+	data) // 钱包兑换
+export const memberprofitlogchangeout = (data) => http.post(prefix +
+	'/blade_member_profit_log/memberprofitlog/changeOut', data) // 转出
+export const memberprofitlogmemberProfitDetail = (data) => http.post(prefix +
+	'/blade_member_profit_log/memberprofitlog/memberProfitDetail', data) // 余额宝总额
+/**
+ *  新闻相关
+ */
+export const newList = (params) => http.post(prefix + '/blade-quotes/quotesIndex/qnews', params) // 新闻列表
+
+/**
+ * 首页轮播图
+ */
+export const carouselQueryAll = (params) => http.post(prefix + '/blade_carousel/carousel/queryAll',
+	params) // 首页轮播图
+
+/**
+ * 在线留言
+ */
+export const messageList = () => http.get(prefix + '/message_board/messageboard/list') // 留言列表
+export const messageSave = (params) => http.post(prefix + '/message_board/messageboard/save/describe',
+	params) // 留言提交
+
+/**
+ * 实名认证
+ */
+export const certification = (params) => http.post(prefix + '/blade-member/member/certification',
+	params) // 留言提交
+export const memberDetail = () => http.get(prefix + '/blade-member/member/memberDetail') // 
+export const memberUpdate = (params) => http.post(prefix + '/blade-member/member/update', params) // 更新用户信息
+
+/**
+ * 银行卡系列
+ */
+export const bankList = (params) => http.post(prefix + '/blade_bank/bank/bankList', params) // 银行卡列表
+export const bankSave = (params) => http.post(prefix + '/blade_bank/bank/save', params) // 添加银行卡
+export const bankUpdate = (params) => http.post(prefix + '/blade_bank/bank/update', params) // 修改银行卡
+export const bankRemove = (params) => http.post(prefix + '/blade_bank/bank/removeById', params) // 解绑银行卡
+export const bankDetail = () => http.get(prefix + '/blade_bank/bank/detail') // 银行卡详情
+
+/**
+ * 签到
+ */
+export const sign = () => http.get(prefix + '/blade_sign_log/signlog/sign') // 每日签到
+export const signDetail = () => http.get(prefix + '/blade_sign_log/signlog/signDetail') // 获取当天签到信息
+/**
+ * 我的团队
+ */
+export const selectMemberRelationList = () => http.get(prefix +
+	'/blade-member/member/selectMemberRelationList') // 银行卡详情
+/**
+ * 首页菜单
+ */
+export const indexmenuQueryAll = () => http.get(prefix + '/blade_index_menu/indexmenu/queryAll') // 首页数据列表
+export const indexmenuDetail = (data) => http.get(prefix + '/blade_index_menu/indexmenu/detail?id=' +
+	data) // 菜单详情
+export const noticePage = (data) => http.get(prefix + '/blade-desk/notice/list?current=' + data.current +
+	'&size=' +
+	data.size) // 首页数据列表
+export const noticeDetail = (data) => http.get(prefix + '/blade-desk/notice/detail?id=' + data) // 菜单详情
+/**
+ * 二维码详情
+ */
+export const downloadconfDetail = (data) => http.get(prefix + '/blade_download_conf/downloadconf/detail')
+export const awardDetail = (data) => http.get(prefix + '/blade_award/award/detail') //制度详情
+
+export const investmentOrderContract = (data) => http.post(prefix +
+	'/blade_investment_order/investmentorder/getContract', data) //合同信息
+
+export const wxLogin = (data) => http.post(prefix + '/blade-auth/oauth/tokenWeb', {
+	...data
+})
+
+// 更新用户基本信息(昵称和头像)
+export const updateProfile = (data) => http.post(prefix + '/blade-member/member/updateProfile', {
+	...data
+})
+
+export const getWxPayParams = (orderId) => http.get(prefix + '/blade-order/wxpay/create', {
+	params: {
+		orderId
+	}
+})
+
+// 首页菜单查询
+export const getIndexMenu = () => http.get(prefix + '/blade_index_menu/indexmenu/queryAll')
+
+/**
+ * 订单相关
+ */
+export const saveOrder = (data) => http.post(prefix + '/blade-order/order/save', data) // 提交订单
+export const orderList = (data) => http.post(prefix + '/blade-order/order/list', data) // 订单列表
+export const orderCancel = (data) => http.post(prefix + '/blade-order/order/cancel', data) // 取消订单
+export const orderPay = (data) => http.post(prefix + '/blade-order/order/pay', data) // 订单支付
+
+// 获取文章详情
+export function getArticleDetail(id) {
+	return http.get(prefix + '/blade-article/article/detail?id=' + id);
+}
+
+/**
+ * 邀请相关接口
+ */
+// 通过账号查询用户
+export const findByAccount = (account) => http.get(prefix + '/blade-member/member/findByAccount?account=' + account)
+// 发送邀请
+export const sendInvitation = (inviteeMemberId) => http.post(prefix +
+	'/blade-member/member/sendInvitation?inviteeMemberId=' + inviteeMemberId)
+// 接受邀请
+export const acceptInvitation = (inviterMemberId) => http.post(prefix +
+	'/blade-member/member/acceptInvitation?inviterMemberId=' + inviterMemberId)
+// 获取邀请记录
+export const getMyInvitations = () => http.get(prefix + '/blade-member/member/getMyInvitations')
+// 设置推荐人
+export const setReferrer = (data) => http.post(prefix + '/blade-member/member/setReferrer', data)
+// 获取用户详情
+export const getMemberDetail = (id) => http.get(prefix + '/blade-member/member/detail/' + id)
+/**
+ * 首页获取用户详情
+ */
+export const getIndexMemberDetail = (id) => http.get(prefix + '/blade-member/member/indexMemberDetail/' + id)
+// 拒绝邀请
+export const rejectInvitation = (inviterMemberId) => http.post(prefix +
+	'/blade-member/member/rejectInvitation?inviterMemberId=' + inviterMemberId)
+// 获取我收到的待处理邀请
+export const getPendingInvitations = () => http.get(prefix + '/blade-member/member/getPendingInvitations')

+ 57 - 0
config/mixins/index.js

@@ -0,0 +1,57 @@
+import store from '@/store'
+export default {
+	data() {
+		return {
+			pattern: '',
+			fontMain: '',
+			bgMain: ''
+		}
+	},
+	onShow() {
+		this.themeInit()
+	},
+	onReady() {
+		this.themeInit()
+	},
+
+	methods: {
+
+		themeInit() {
+			this.pattern = store.getters.pattern
+			this.setNavbar(this.pattern)
+			this.setTabbar(this.pattern)
+			this.fontMain = this.pattern == 'dark' ? '#FFFFFF' : '#000000'
+			this.bgMain = this.pattern == 'dark' ? '#102030' : '#F6F6F6'
+		},
+		setNavbar(item) {
+			if (item == 'dark') {
+				uni.setNavigationBarColor({
+					frontColor: "#ffffff",
+					backgroundColor: "#102030"
+				})
+			} else {
+				uni.setNavigationBarColor({
+					frontColor: "white",
+					backgroundColor: "rgb(9 56 106)"
+				})
+			}
+		},
+		setTabbar(item) {
+			if (item == 'dark') {
+				uni.setTabBarStyle({
+					color: '#a2a6a5',
+					selectedColor: '#1881d2',
+					backgroundColor: '#16263e',
+					borderStyle: 'black'
+				})
+			} else {
+				uni.setTabBarStyle({
+					color: '#999999',
+					selectedColor: '#292C35',
+					backgroundColor: '#ffffff',
+					borderStyle: 'white'
+				})
+			}
+		},
+	}
+}

+ 183 - 0
config/request.js

@@ -0,0 +1,183 @@
+let base64 = require('../utils/base64.js').Base64;
+import {
+	http,
+	toast
+} from '@/uni_modules/uview-plus'
+
+import {
+	DEVE_URL,
+	PRODUCT_URL
+} from '@/common/config.js'
+import store from '@/store/index.js'
+
+const CODE_MESSAGE = {
+	0: '未可知错误,可能是因为后端不支持跨域CORS、接口地址不存在等问题引起',
+	200: '服务器成功返回请求数据',
+	201: '新建或修改数据成功',
+	202: '一个请求已经进入后台排队(异步任务)',
+	204: '删除数据成功',
+	400: '发出信息有误',
+	401: '用户没有权限(令牌失效、用户名、密码错误、登录过期)',
+	402: '令牌过期',
+	403: '用户得到授权,但是访问是被禁止的',
+	404: '访问资源不存在',
+	406: '请求格式不可得',
+	410: '请求资源被永久删除,且不会被看到',
+	500: '服务器发生错误',
+	502: '网关错误',
+	503: '服务不可用,服务器暂时过载或维护',
+	504: '网关超时',
+}
+
+let refreshToking = false
+let requests = []
+
+let baseURL = ''
+if (process.env.NODE_ENV === 'development') {
+	baseURL = DEVE_URL
+} else {
+	baseURL = PRODUCT_URL
+}
+// #ifdef H5
+baseURL = ''
+// #endif
+
+import {
+	login
+} from '../config/api.js';
+import Vue from 'vue'
+
+// 此vm参数为页面的实例,可以通过它引用vuex中的变量
+const requestInterceptors = (vm) => {
+
+	/**
+	 * axios请求拦截器配置
+	 * @param config
+	 * @returns {any}
+	 */
+	const requestConf = (config) => {
+		// 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
+		config.data = config.data || {}
+		let token = uni.getStorageSync('access_token');
+		if (token) config.header['Blade-Auth'] = `Bearer ${token}`
+		// if (token) config.headers['Blade-Auth'] = `Bearer ${token}`
+
+		// 根据custom参数中配置的是否需要token,添加对应的请求头
+		config.header['Authorization'] = `Basic ${base64.encode('saber:saber_secret')}`
+
+		// uni.showLoading({
+		// 	title: '加载中...',
+		// 	mask: true
+		// })
+		return config
+	}
+
+	/**
+	 * 刷新刷新令牌
+	 * @param config 过期请求配置
+	 * @returns {any} 返回结果
+	 */
+	const tryRefreshToken = async (config) => {
+		if (!refreshToking) {
+			refreshToking = true
+			try {
+				const params = {}
+				params.grantType = 'refresh_token'
+				params.refreshToken = uni.getStorageSync('refresh_token')
+				const data = await store.dispatch('refreshToken', params);
+				console.log('刷新成功,返回:')
+				console.log(data)
+				if (data.access_token) {
+					requests.forEach((cb) => cb(data.access_token))
+					requests = []
+					return uni.$u.http.middleware(requestConf(config))
+				}
+			} catch (error) {
+				console.error('refreshToken error =>', error)
+				// 使用 store 的 action 处理未登录状态
+				const pages = getCurrentPages();
+				const currentPage = pages[pages.length - 1];
+				const redirectPath = currentPage ? currentPage.route : '';
+				store.dispatch('handleUnauthorized', redirectPath);
+				return Promise.reject(error)
+			} finally {
+				refreshToking = false
+			}
+		} else {
+			return new Promise((resolve) => {
+				requests.push(() => {
+					resolve(uni.$u.http.middleware(requestConf(config)))
+				})
+			})
+		}
+	}
+
+	// 初始化请求配置
+	uni.$u.http.setConfig((config) => {
+		/* config 为默认全局配置*/
+		config.baseURL = baseURL; /* 根域名 */
+		return config
+	})
+
+	// 请求拦截
+	uni.$u.http.interceptors.request.use(requestConf, config => { // 可使用async await 做异步操作
+		return Promise.reject(config)
+	})
+
+	// 响应拦截
+	uni.$u.http.interceptors.response.use(async (response) => {
+		uni.hideLoading()
+		/* 对响应成功做点什么 可使用async await 做异步操作*/
+		const data = response.data
+		if (data.success && data.success == 'true' || data.Code == 0) {
+			return data
+		}
+		
+		// 处理未登录和token过期情况
+		if (data.code === 401 || data.code === 402) {
+			store.commit('userInfo', {});
+			store.commit('isLogin', false);
+			store.commit('access_token', '');
+			store.commit('refresh_token', '');
+			
+			// 显示提示
+			uni.$u.toast(data.code === 401 ? '请先登录' : 'token已过期,请重新登录');
+			
+			// 获取当前页面路径
+			const pages = getCurrentPages();
+			const currentPage = pages[pages.length - 1];
+			const redirectUrl = currentPage ? encodeURIComponent(currentPage.route) : '';
+			
+			// 延迟跳转,让用户看到提示
+			setTimeout(() => {
+				uni.navigateTo({
+					url: `/pages/user/login?redirect=/${redirectUrl}`
+				});
+			}, 1500);
+			
+			return Promise.reject(data);
+		}
+		
+		// 处理其他状态码
+		switch (data.code) {
+			case 200:
+			case 201:
+				return data;
+			case 403:
+				return vm.$route('/pages/403');
+			case 500:
+				uni.$u.toast('服务器响应失败');
+				return Promise.reject(data);
+		}
+
+		// 其他错误统一提示
+		const errMsg = data.msg || data.error_description || CODE_MESSAGE[data.code] || '未知错误';
+		uni.$u.toast(errMsg);
+		return Promise.reject(data);
+	}, (error) => {
+		uni.hideLoading();
+		// 处理网络错误等
+		uni.$u.toast('网络请求失败,请检查网络连接');
+		return Promise.reject(error);
+	})
+}

+ 24 - 0
config/request/index.js

@@ -0,0 +1,24 @@
+// 引入配置
+import config from '@/common/config'
+// 引入拦截器配置
+import {
+	requestInterceptors,
+	responseInterceptors
+} from './interceptors.js'
+// 引入luch-request
+import {
+	http
+} from '@/uni_modules/uview-plus'
+//  初始化请求配置
+const initRequest = (vm) => {
+	http.setConfig((defaultConfig) => {
+		/* defaultConfig 为默认全局配置 */
+		defaultConfig.baseURL = config.baseUrl /* 根域名 */
+		return defaultConfig
+	})
+	requestInterceptors()
+	responseInterceptors()
+}
+export {
+	initRequest
+}

+ 214 - 0
config/request/interceptors.js

@@ -0,0 +1,214 @@
+import {
+	http,
+	toast
+} from '@/uni_modules/uview-plus'
+import store from '@/store'
+import {
+	DEVE_URL,
+	PRODUCT_URL
+} from '@/common/config.js'
+import {
+	login
+} from '../../config/api.js'
+
+
+import {
+	Base64
+} from 'js-base64'
+const CODE_MESSAGE = {
+	0: '未可知错误,可能是因为后端不支持跨域CORS、接口地址不存在等问题引起',
+	200: '服务器成功返回请求数据',
+	201: '新建或修改数据成功',
+	202: '一个请求已经进入后台排队(异步任务)',
+	204: '删除数据成功',
+	400: '发出信息有误',
+	401: '用户没有权限(令牌失效、用户名、密码错误、登录过期)',
+	402: '令牌过期',
+	403: '用户得到授权,但是访问是被禁止的',
+	404: '访问资源不存在',
+	406: '请求格式不可得',
+	410: '请求资源被永久删除,且不会被看到',
+	500: '服务器发生错误',
+	502: '网关错误',
+	503: '服务不可用,服务器暂时过载或维护',
+	504: '网关超时',
+}
+
+let refreshToking = false
+let requests = []
+
+let baseURL = process.env.NODE_ENV === 'development' ? DEVE_URL : PRODUCT_URL
+// #ifdef H5
+baseURL = ''
+// #endif
+
+// 全局配置
+http.setConfig((config) => {
+	config.baseURL = baseURL
+	return config
+})
+
+let loadingTimer = null;
+let loadingCount = 0;
+
+const showLoading = () => {
+	loadingCount++;
+	if (loadingCount === 1) {
+		// 延迟显示loading,避免闪烁
+		loadingTimer = setTimeout(() => {
+			uni.showLoading({
+				mask: true
+			});
+		}, 300); // 300ms延迟
+	}
+};
+
+const hideLoading = () => {
+	loadingCount--;
+	if (loadingCount === 0) {
+		if (loadingTimer) {
+			clearTimeout(loadingTimer);
+			loadingTimer = null;
+		}
+		uni.hideLoading();
+
+	}
+};
+
+const requestInterceptors = (vm) => {
+	/**
+	 * 请求拦截
+	 */
+	http.interceptors.request.use((config) => {
+		config.baseURL = baseURL
+		// 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
+		config.data = config.data || {}
+		// 添加token
+		let token = uni.getStorageSync('access_token')
+		if (token) config.header['Blade-Auth'] = `Bearer ${token}`
+
+		// 添加基础认证
+		config.header['Authorization'] = `Basic ${Base64.encode('saber:saber_secret')}`
+
+		uni.showLoading({
+			mask: true
+		})
+		setTimeout(() => {
+			uni.hideLoading();
+		}, 5000)
+		return config
+	}, (config) => {
+		return Promise.reject(config)
+	})
+}
+
+/**
+ * 刷新令牌
+ */
+const tryRefreshToken = async (config) => {
+	if (!refreshToking) {
+		refreshToking = true
+		try {
+			const params = {
+				grantType: 'refresh_token',
+				refreshToken: uni.getStorageSync('refresh_token')
+			}
+
+			const data = await store.dispatch('refreshToken', params)
+
+			if (data.access_token) {
+				requests.forEach((cb) => cb(data.access_token))
+				requests = []
+				return http.request(config)
+			}
+		} catch (error) {
+			console.error('refreshToken error =>', error)
+			store.commit('userInfo', {})
+			store.commit('isLogin', false)
+			store.commit('access_token', '')
+			store.commit('refresh_token', '')
+
+			// 移除自动跳转到登录页面
+			toast('请登录后操作')
+			// 添加跳转到登录页面的逻辑
+			uni.navigateTo({
+				url: '/packageUser/pages/login/index'
+			});
+			return Promise.reject(error)
+		} finally {
+			refreshToking = false
+		}
+	} else {
+		return new Promise((resolve) => {
+			requests.push(() => {
+				resolve(http.request(config))
+			})
+		})
+	}
+}
+
+const responseInterceptors = (vm) => {
+	/**
+	 * 响应拦截
+	 */
+	http.interceptors.response.use(async (response) => {
+		const data = response.data
+		uni.hideLoading()
+		// 成功状态判断
+		if (data.success && data.success == 'true' || data.Code == 0) {
+			return data
+		}
+
+		// 状态码处理
+		switch (data.code) {
+			case 200:
+			case 201:
+				return data
+			case 401:
+			case 402:
+				// token过期,尝试刷新
+				return await tryRefreshToken(response.config)
+			case 403:
+				uni.navigateTo({
+					url: '/pages/error/403'
+				})
+				return Promise.reject(data)
+			case 500:
+				toast('服务器错误,请稍后重试')
+				return Promise.reject(data)
+			default:
+				// 错误提示
+				const errMsg = data?.msg || data?.error_description || CODE_MESSAGE[data.code] || '请求失败'
+				toast(errMsg)
+
+				return Promise.reject(data)
+		}
+	}, async (error) => {
+		hideLoading();
+		uni.hideLoading();
+		// HTTP状态码处理
+		switch (error.statusCode) {
+			case 401:
+			case 402:
+				return await tryRefreshToken(error.config)
+			case 403:
+				uni.navigateTo({
+					url: '/pages/error/403'
+				})
+				break
+			case 500:
+				toast('服务器错误,请稍后重试')
+				break
+			default:
+				toast('网络错误,请检查网络连接')
+		}
+
+		return Promise.reject(error)
+	})
+
+}
+
+export {
+	requestInterceptors,
+	responseInterceptors
+}

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+	<script>
+	  var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+	    CSS.supports('top: constant(a)'))
+	  document.write(
+	    '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+	    (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+	</script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 44 - 0
js_sdk/js-base64/.attic/test-moment/dankogai.js

@@ -0,0 +1,44 @@
+/*
+ * $Id: dankogai.js,v 0.4 2012/08/24 05:23:18 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+describe('basic', function () {
+    it('d',    is(Base64.encode('d'),    'ZA=='));
+    it('da',   is(Base64.encode('da'),   'ZGE='));
+    it('dan',  is(Base64.encode('dan'),  'ZGFu'));
+    it('ZA==', is(Base64.decode('ZA=='), 'd'   ));
+    it('ZGE=', is(Base64.decode('ZGE='), 'da'  ));
+    it('ZGFu', is(Base64.decode('ZGFu'), 'dan' ));
+});
+
+describe('whitespace', function () {
+    it('Z A==', is(Base64.decode('ZA =='), 'd'   ));
+    it('ZG E=', is(Base64.decode('ZG E='), 'da'  ));
+    it('ZGF u', is(Base64.decode('ZGF u'), 'dan' ));
+});
+
+describe('null', function () {
+    it('\\0',       is(Base64.encode('\0'),     'AA=='));
+    it('\\0\\0',    is(Base64.encode('\0\0'),   'AAA='));
+    it('\\0\\0\\0', is(Base64.encode('\0\0\0'), 'AAAA'));
+    it('AA==',      is(Base64.decode('AA=='), '\0'    ));
+    it('AAA=',      is(Base64.decode('AAA='), '\0\0'  ));
+    it('AAAA',      is(Base64.decode('AAAA'), '\0\0\0'));
+});
+
+describe('Base64', function () {
+    it('.encode', is(Base64.encode('小飼弾'), '5bCP6aO85by+'));
+    it('.encodeURI', is(Base64.encodeURI('小飼弾'), '5bCP6aO85by-'));
+    it('.decode', is(Base64.decode('5bCP6aO85by+'), '小飼弾'));
+    it('.decode', is(Base64.decode('5bCP6aO85by-'), '小飼弾'));
+});

+ 24 - 0
js_sdk/js-base64/.attic/test-moment/es5.js

@@ -0,0 +1,24 @@
+/*
+ * $Id: es5.js,v 0.1 2012/08/23 19:43:17 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+if ('extendString' in Base64){
+    Base64.extendString();
+    describe('String', function () {
+        it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
+        it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
+        it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
+        it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
+        it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
+    });
+}

+ 25 - 0
js_sdk/js-base64/.attic/test-moment/es6.js

@@ -0,0 +1,25 @@
+/*
+ * $Id: es6.js,v 0.1 2017/11/29 21:43:17 ufolux Exp ufolux $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+import {Base64} from '../base64'
+
+var assert = assert || require("assert");
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+if ('extendString' in Base64){
+    Base64.extendString();
+    describe('String', function () {
+        it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
+        it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
+        it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
+        it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
+        it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
+    });
+}

+ 40 - 0
js_sdk/js-base64/.attic/test-moment/index.html

@@ -0,0 +1,40 @@
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
+  </head>
+  <body>
+    <div id="mocha"></div>
+
+    <script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
+    <script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>
+    <script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
+    <script>
+      mocha.setup('bdd');
+    </script>
+    <script src="./moment.js"></script>
+    <script src="../base64.js"></script>
+    <script>
+      var assert = function(expr, msg) {
+        if (!expr) throw new Error(msg || 'failed');
+      };
+      assert.equal = function(a, b, msg) {
+        if (a !== b) throw new Error(msg || ('failed : '+a+','+b));
+      };
+    </script>
+    <script src="./dankogai.js"></script>
+    <script src="./es5.js"></script>
+    <script src="./large.js"></script>
+    <script src="./yoshinoya.js"></script>
+    <script>
+      $(function() {
+        mocha.run();
+      });
+    </script>
+</head>
+<body>
+  $Id: index.html,v 0.3 2017/09/11 08:43:43 dankogai Exp dankogai $
+  <div id="mocha"></div>
+</body>
+</html>

+ 25 - 0
js_sdk/js-base64/.attic/test-moment/large.js

@@ -0,0 +1,25 @@
+/*
+ * $Id: large.js,v 0.3 2012/08/23 19:14:37 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ *   http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+var seed = function () {
+    var a, i;
+    for (a = [], i = 0; i < 256; i++) {
+        a.push(String.fromCharCode(i));
+    }
+    return a.join('');
+}();
+describe('Base64', function () {
+    for (var i = 0, str = seed; i < 16; str += str, i++) {
+        it(''+str.length, is(Base64.decode(Base64.encode(str)), str));
+    }
+});

+ 4535 - 0
js_sdk/js-base64/.attic/test-moment/moment.js

@@ -0,0 +1,4535 @@
+//! moment.js
+//! version : 2.20.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, (function () { 'use strict';
+
+var hookCallback;
+
+function hooks () {
+    return hookCallback.apply(null, arguments);
+}
+
+// This is done to register the method called with moment()
+// without creating circular dependencies.
+function setHookCallback (callback) {
+    hookCallback = callback;
+}
+
+function isArray(input) {
+    return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
+}
+
+function isObject(input) {
+    // IE8 will treat undefined and null as object if it wasn't for
+    // input != null
+    return input != null && Object.prototype.toString.call(input) === '[object Object]';
+}
+
+function isObjectEmpty(obj) {
+    if (Object.getOwnPropertyNames) {
+        return (Object.getOwnPropertyNames(obj).length === 0);
+    } else {
+        var k;
+        for (k in obj) {
+            if (obj.hasOwnProperty(k)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+function isUndefined(input) {
+    return input === void 0;
+}
+
+function isNumber(input) {
+    return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
+}
+
+function isDate(input) {
+    return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
+}
+
+function map(arr, fn) {
+    var res = [], i;
+    for (i = 0; i < arr.length; ++i) {
+        res.push(fn(arr[i], i));
+    }
+    return res;
+}
+
+function hasOwnProp(a, b) {
+    return Object.prototype.hasOwnProperty.call(a, b);
+}
+
+function extend(a, b) {
+    for (var i in b) {
+        if (hasOwnProp(b, i)) {
+            a[i] = b[i];
+        }
+    }
+
+    if (hasOwnProp(b, 'toString')) {
+        a.toString = b.toString;
+    }
+
+    if (hasOwnProp(b, 'valueOf')) {
+        a.valueOf = b.valueOf;
+    }
+
+    return a;
+}
+
+function createUTC (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, true).utc();
+}
+
+function defaultParsingFlags() {
+    // We need to deep clone this object.
+    return {
+        empty           : false,
+        unusedTokens    : [],
+        unusedInput     : [],
+        overflow        : -2,
+        charsLeftOver   : 0,
+        nullInput       : false,
+        invalidMonth    : null,
+        invalidFormat   : false,
+        userInvalidated : false,
+        iso             : false,
+        parsedDateParts : [],
+        meridiem        : null,
+        rfc2822         : false,
+        weekdayMismatch : false
+    };
+}
+
+function getParsingFlags(m) {
+    if (m._pf == null) {
+        m._pf = defaultParsingFlags();
+    }
+    return m._pf;
+}
+
+var some;
+if (Array.prototype.some) {
+    some = Array.prototype.some;
+} else {
+    some = function (fun) {
+        var t = Object(this);
+        var len = t.length >>> 0;
+
+        for (var i = 0; i < len; i++) {
+            if (i in t && fun.call(this, t[i], i, t)) {
+                return true;
+            }
+        }
+
+        return false;
+    };
+}
+
+function isValid(m) {
+    if (m._isValid == null) {
+        var flags = getParsingFlags(m);
+        var parsedParts = some.call(flags.parsedDateParts, function (i) {
+            return i != null;
+        });
+        var isNowValid = !isNaN(m._d.getTime()) &&
+            flags.overflow < 0 &&
+            !flags.empty &&
+            !flags.invalidMonth &&
+            !flags.invalidWeekday &&
+            !flags.weekdayMismatch &&
+            !flags.nullInput &&
+            !flags.invalidFormat &&
+            !flags.userInvalidated &&
+            (!flags.meridiem || (flags.meridiem && parsedParts));
+
+        if (m._strict) {
+            isNowValid = isNowValid &&
+                flags.charsLeftOver === 0 &&
+                flags.unusedTokens.length === 0 &&
+                flags.bigHour === undefined;
+        }
+
+        if (Object.isFrozen == null || !Object.isFrozen(m)) {
+            m._isValid = isNowValid;
+        }
+        else {
+            return isNowValid;
+        }
+    }
+    return m._isValid;
+}
+
+function createInvalid (flags) {
+    var m = createUTC(NaN);
+    if (flags != null) {
+        extend(getParsingFlags(m), flags);
+    }
+    else {
+        getParsingFlags(m).userInvalidated = true;
+    }
+
+    return m;
+}
+
+// Plugins that add properties should also add the key here (null value),
+// so we can properly clone ourselves.
+var momentProperties = hooks.momentProperties = [];
+
+function copyConfig(to, from) {
+    var i, prop, val;
+
+    if (!isUndefined(from._isAMomentObject)) {
+        to._isAMomentObject = from._isAMomentObject;
+    }
+    if (!isUndefined(from._i)) {
+        to._i = from._i;
+    }
+    if (!isUndefined(from._f)) {
+        to._f = from._f;
+    }
+    if (!isUndefined(from._l)) {
+        to._l = from._l;
+    }
+    if (!isUndefined(from._strict)) {
+        to._strict = from._strict;
+    }
+    if (!isUndefined(from._tzm)) {
+        to._tzm = from._tzm;
+    }
+    if (!isUndefined(from._isUTC)) {
+        to._isUTC = from._isUTC;
+    }
+    if (!isUndefined(from._offset)) {
+        to._offset = from._offset;
+    }
+    if (!isUndefined(from._pf)) {
+        to._pf = getParsingFlags(from);
+    }
+    if (!isUndefined(from._locale)) {
+        to._locale = from._locale;
+    }
+
+    if (momentProperties.length > 0) {
+        for (i = 0; i < momentProperties.length; i++) {
+            prop = momentProperties[i];
+            val = from[prop];
+            if (!isUndefined(val)) {
+                to[prop] = val;
+            }
+        }
+    }
+
+    return to;
+}
+
+var updateInProgress = false;
+
+// Moment prototype object
+function Moment(config) {
+    copyConfig(this, config);
+    this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+    if (!this.isValid()) {
+        this._d = new Date(NaN);
+    }
+    // Prevent infinite loop in case updateOffset creates new moment
+    // objects.
+    if (updateInProgress === false) {
+        updateInProgress = true;
+        hooks.updateOffset(this);
+        updateInProgress = false;
+    }
+}
+
+function isMoment (obj) {
+    return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
+}
+
+function absFloor (number) {
+    if (number < 0) {
+        // -0 -> 0
+        return Math.ceil(number) || 0;
+    } else {
+        return Math.floor(number);
+    }
+}
+
+function toInt(argumentForCoercion) {
+    var coercedNumber = +argumentForCoercion,
+        value = 0;
+
+    if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+        value = absFloor(coercedNumber);
+    }
+
+    return value;
+}
+
+// compare two arrays, return the number of differences
+function compareArrays(array1, array2, dontConvert) {
+    var len = Math.min(array1.length, array2.length),
+        lengthDiff = Math.abs(array1.length - array2.length),
+        diffs = 0,
+        i;
+    for (i = 0; i < len; i++) {
+        if ((dontConvert && array1[i] !== array2[i]) ||
+            (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
+            diffs++;
+        }
+    }
+    return diffs + lengthDiff;
+}
+
+function warn(msg) {
+    if (hooks.suppressDeprecationWarnings === false &&
+            (typeof console !==  'undefined') && console.warn) {
+        console.warn('Deprecation warning: ' + msg);
+    }
+}
+
+function deprecate(msg, fn) {
+    var firstTime = true;
+
+    return extend(function () {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(null, msg);
+        }
+        if (firstTime) {
+            var args = [];
+            var arg;
+            for (var i = 0; i < arguments.length; i++) {
+                arg = '';
+                if (typeof arguments[i] === 'object') {
+                    arg += '\n[' + i + '] ';
+                    for (var key in arguments[0]) {
+                        arg += key + ': ' + arguments[0][key] + ', ';
+                    }
+                    arg = arg.slice(0, -2); // Remove trailing comma and space
+                } else {
+                    arg = arguments[i];
+                }
+                args.push(arg);
+            }
+            warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
+            firstTime = false;
+        }
+        return fn.apply(this, arguments);
+    }, fn);
+}
+
+var deprecations = {};
+
+function deprecateSimple(name, msg) {
+    if (hooks.deprecationHandler != null) {
+        hooks.deprecationHandler(name, msg);
+    }
+    if (!deprecations[name]) {
+        warn(msg);
+        deprecations[name] = true;
+    }
+}
+
+hooks.suppressDeprecationWarnings = false;
+hooks.deprecationHandler = null;
+
+function isFunction(input) {
+    return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
+}
+
+function set (config) {
+    var prop, i;
+    for (i in config) {
+        prop = config[i];
+        if (isFunction(prop)) {
+            this[i] = prop;
+        } else {
+            this['_' + i] = prop;
+        }
+    }
+    this._config = config;
+    // Lenient ordinal parsing accepts just a number in addition to
+    // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+    // TODO: Remove "ordinalParse" fallback in next major release.
+    this._dayOfMonthOrdinalParseLenient = new RegExp(
+        (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+            '|' + (/\d{1,2}/).source);
+}
+
+function mergeConfigs(parentConfig, childConfig) {
+    var res = extend({}, parentConfig), prop;
+    for (prop in childConfig) {
+        if (hasOwnProp(childConfig, prop)) {
+            if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                res[prop] = {};
+                extend(res[prop], parentConfig[prop]);
+                extend(res[prop], childConfig[prop]);
+            } else if (childConfig[prop] != null) {
+                res[prop] = childConfig[prop];
+            } else {
+                delete res[prop];
+            }
+        }
+    }
+    for (prop in parentConfig) {
+        if (hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])) {
+            // make sure changes to properties don't modify parent config
+            res[prop] = extend({}, res[prop]);
+        }
+    }
+    return res;
+}
+
+function Locale(config) {
+    if (config != null) {
+        this.set(config);
+    }
+}
+
+var keys;
+
+if (Object.keys) {
+    keys = Object.keys;
+} else {
+    keys = function (obj) {
+        var i, res = [];
+        for (i in obj) {
+            if (hasOwnProp(obj, i)) {
+                res.push(i);
+            }
+        }
+        return res;
+    };
+}
+
+var defaultCalendar = {
+    sameDay : '[Today at] LT',
+    nextDay : '[Tomorrow at] LT',
+    nextWeek : 'dddd [at] LT',
+    lastDay : '[Yesterday at] LT',
+    lastWeek : '[Last] dddd [at] LT',
+    sameElse : 'L'
+};
+
+function calendar (key, mom, now) {
+    var output = this._calendar[key] || this._calendar['sameElse'];
+    return isFunction(output) ? output.call(mom, now) : output;
+}
+
+var defaultLongDateFormat = {
+    LTS  : 'h:mm:ss A',
+    LT   : 'h:mm A',
+    L    : 'MM/DD/YYYY',
+    LL   : 'MMMM D, YYYY',
+    LLL  : 'MMMM D, YYYY h:mm A',
+    LLLL : 'dddd, MMMM D, YYYY h:mm A'
+};
+
+function longDateFormat (key) {
+    var format = this._longDateFormat[key],
+        formatUpper = this._longDateFormat[key.toUpperCase()];
+
+    if (format || !formatUpper) {
+        return format;
+    }
+
+    this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
+        return val.slice(1);
+    });
+
+    return this._longDateFormat[key];
+}
+
+var defaultInvalidDate = 'Invalid date';
+
+function invalidDate () {
+    return this._invalidDate;
+}
+
+var defaultOrdinal = '%d';
+var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
+
+function ordinal (number) {
+    return this._ordinal.replace('%d', number);
+}
+
+var defaultRelativeTime = {
+    future : 'in %s',
+    past   : '%s ago',
+    s  : 'a few seconds',
+    ss : '%d seconds',
+    m  : 'a minute',
+    mm : '%d minutes',
+    h  : 'an hour',
+    hh : '%d hours',
+    d  : 'a day',
+    dd : '%d days',
+    M  : 'a month',
+    MM : '%d months',
+    y  : 'a year',
+    yy : '%d years'
+};
+
+function relativeTime (number, withoutSuffix, string, isFuture) {
+    var output = this._relativeTime[string];
+    return (isFunction(output)) ?
+        output(number, withoutSuffix, string, isFuture) :
+        output.replace(/%d/i, number);
+}
+
+function pastFuture (diff, output) {
+    var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+    return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+}
+
+var aliases = {};
+
+function addUnitAlias (unit, shorthand) {
+    var lowerCase = unit.toLowerCase();
+    aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+}
+
+function normalizeUnits(units) {
+    return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
+}
+
+function normalizeObjectUnits(inputObject) {
+    var normalizedInput = {},
+        normalizedProp,
+        prop;
+
+    for (prop in inputObject) {
+        if (hasOwnProp(inputObject, prop)) {
+            normalizedProp = normalizeUnits(prop);
+            if (normalizedProp) {
+                normalizedInput[normalizedProp] = inputObject[prop];
+            }
+        }
+    }
+
+    return normalizedInput;
+}
+
+var priorities = {};
+
+function addUnitPriority(unit, priority) {
+    priorities[unit] = priority;
+}
+
+function getPrioritizedUnits(unitsObj) {
+    var units = [];
+    for (var u in unitsObj) {
+        units.push({unit: u, priority: priorities[u]});
+    }
+    units.sort(function (a, b) {
+        return a.priority - b.priority;
+    });
+    return units;
+}
+
+function zeroFill(number, targetLength, forceSign) {
+    var absNumber = '' + Math.abs(number),
+        zerosToFill = targetLength - absNumber.length,
+        sign = number >= 0;
+    return (sign ? (forceSign ? '+' : '') : '-') +
+        Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+}
+
+var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
+
+var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
+
+var formatFunctions = {};
+
+var formatTokenFunctions = {};
+
+// token:    'M'
+// padded:   ['MM', 2]
+// ordinal:  'Mo'
+// callback: function () { this.month() + 1 }
+function addFormatToken (token, padded, ordinal, callback) {
+    var func = callback;
+    if (typeof callback === 'string') {
+        func = function () {
+            return this[callback]();
+        };
+    }
+    if (token) {
+        formatTokenFunctions[token] = func;
+    }
+    if (padded) {
+        formatTokenFunctions[padded[0]] = function () {
+            return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+        };
+    }
+    if (ordinal) {
+        formatTokenFunctions[ordinal] = function () {
+            return this.localeData().ordinal(func.apply(this, arguments), token);
+        };
+    }
+}
+
+function removeFormattingTokens(input) {
+    if (input.match(/\[[\s\S]/)) {
+        return input.replace(/^\[|\]$/g, '');
+    }
+    return input.replace(/\\/g, '');
+}
+
+function makeFormatFunction(format) {
+    var array = format.match(formattingTokens), i, length;
+
+    for (i = 0, length = array.length; i < length; i++) {
+        if (formatTokenFunctions[array[i]]) {
+            array[i] = formatTokenFunctions[array[i]];
+        } else {
+            array[i] = removeFormattingTokens(array[i]);
+        }
+    }
+
+    return function (mom) {
+        var output = '', i;
+        for (i = 0; i < length; i++) {
+            output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
+        }
+        return output;
+    };
+}
+
+// format date using native date object
+function formatMoment(m, format) {
+    if (!m.isValid()) {
+        return m.localeData().invalidDate();
+    }
+
+    format = expandFormat(format, m.localeData());
+    formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+
+    return formatFunctions[format](m);
+}
+
+function expandFormat(format, locale) {
+    var i = 5;
+
+    function replaceLongDateFormatTokens(input) {
+        return locale.longDateFormat(input) || input;
+    }
+
+    localFormattingTokens.lastIndex = 0;
+    while (i >= 0 && localFormattingTokens.test(format)) {
+        format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
+        localFormattingTokens.lastIndex = 0;
+        i -= 1;
+    }
+
+    return format;
+}
+
+var match1         = /\d/;            //       0 - 9
+var match2         = /\d\d/;          //      00 - 99
+var match3         = /\d{3}/;         //     000 - 999
+var match4         = /\d{4}/;         //    0000 - 9999
+var match6         = /[+-]?\d{6}/;    // -999999 - 999999
+var match1to2      = /\d\d?/;         //       0 - 99
+var match3to4      = /\d\d\d\d?/;     //     999 - 9999
+var match5to6      = /\d\d\d\d\d\d?/; //   99999 - 999999
+var match1to3      = /\d{1,3}/;       //       0 - 999
+var match1to4      = /\d{1,4}/;       //       0 - 9999
+var match1to6      = /[+-]?\d{1,6}/;  // -999999 - 999999
+
+var matchUnsigned  = /\d+/;           //       0 - inf
+var matchSigned    = /[+-]?\d+/;      //    -inf - inf
+
+var matchOffset    = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
+var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+
+var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 12.4.8789 123456789.123
+
+// any word (or two) characters or numbers including two/three word month in arabic.
+// includes scottish gaelic two word and hyphenated months
+var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
+
+
+var regexes = {};
+
+function addRegexToken (token, regex, strictRegex) {
+    regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
+        return (isStrict && strictRegex) ? strictRegex : regex;
+    };
+}
+
+function getParseRegexForToken (token, config) {
+    if (!hasOwnProp(regexes, token)) {
+        return new RegExp(unescapeFormat(token));
+    }
+
+    return regexes[token](config._strict, config._locale);
+}
+
+// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+function unescapeFormat(s) {
+    return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
+        return p1 || p2 || p3 || p4;
+    }));
+}
+
+function regexEscape(s) {
+    return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+}
+
+var tokens = {};
+
+function addParseToken (token, callback) {
+    var i, func = callback;
+    if (typeof token === 'string') {
+        token = [token];
+    }
+    if (isNumber(callback)) {
+        func = function (input, array) {
+            array[callback] = toInt(input);
+        };
+    }
+    for (i = 0; i < token.length; i++) {
+        tokens[token[i]] = func;
+    }
+}
+
+function addWeekParseToken (token, callback) {
+    addParseToken(token, function (input, array, config, token) {
+        config._w = config._w || {};
+        callback(input, config._w, config, token);
+    });
+}
+
+function addTimeToArrayFromToken(token, input, config) {
+    if (input != null && hasOwnProp(tokens, token)) {
+        tokens[token](input, config._a, config, token);
+    }
+}
+
+var YEAR = 0;
+var MONTH = 1;
+var DATE = 2;
+var HOUR = 3;
+var MINUTE = 4;
+var SECOND = 5;
+var MILLISECOND = 6;
+var WEEK = 7;
+var WEEKDAY = 8;
+
+// FORMATTING
+
+addFormatToken('Y', 0, 0, function () {
+    var y = this.year();
+    return y <= 9999 ? '' + y : '+' + y;
+});
+
+addFormatToken(0, ['YY', 2], 0, function () {
+    return this.year() % 100;
+});
+
+addFormatToken(0, ['YYYY',   4],       0, 'year');
+addFormatToken(0, ['YYYYY',  5],       0, 'year');
+addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+// ALIASES
+
+addUnitAlias('year', 'y');
+
+// PRIORITIES
+
+addUnitPriority('year', 1);
+
+// PARSING
+
+addRegexToken('Y',      matchSigned);
+addRegexToken('YY',     match1to2, match2);
+addRegexToken('YYYY',   match1to4, match4);
+addRegexToken('YYYYY',  match1to6, match6);
+addRegexToken('YYYYYY', match1to6, match6);
+
+addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+addParseToken('YYYY', function (input, array) {
+    array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+});
+addParseToken('YY', function (input, array) {
+    array[YEAR] = hooks.parseTwoDigitYear(input);
+});
+addParseToken('Y', function (input, array) {
+    array[YEAR] = parseInt(input, 10);
+});
+
+// HELPERS
+
+function daysInYear(year) {
+    return isLeapYear(year) ? 366 : 365;
+}
+
+function isLeapYear(year) {
+    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
+
+// HOOKS
+
+hooks.parseTwoDigitYear = function (input) {
+    return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+};
+
+// MOMENTS
+
+var getSetYear = makeGetSet('FullYear', true);
+
+function getIsLeapYear () {
+    return isLeapYear(this.year());
+}
+
+function makeGetSet (unit, keepTime) {
+    return function (value) {
+        if (value != null) {
+            set$1(this, unit, value);
+            hooks.updateOffset(this, keepTime);
+            return this;
+        } else {
+            return get(this, unit);
+        }
+    };
+}
+
+function get (mom, unit) {
+    return mom.isValid() ?
+        mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
+}
+
+function set$1 (mom, unit, value) {
+    if (mom.isValid() && !isNaN(value)) {
+        if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
+        }
+        else {
+            mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+        }
+    }
+}
+
+// MOMENTS
+
+function stringGet (units) {
+    units = normalizeUnits(units);
+    if (isFunction(this[units])) {
+        return this[units]();
+    }
+    return this;
+}
+
+
+function stringSet (units, value) {
+    if (typeof units === 'object') {
+        units = normalizeObjectUnits(units);
+        var prioritized = getPrioritizedUnits(units);
+        for (var i = 0; i < prioritized.length; i++) {
+            this[prioritized[i].unit](units[prioritized[i].unit]);
+        }
+    } else {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units](value);
+        }
+    }
+    return this;
+}
+
+function mod(n, x) {
+    return ((n % x) + x) % x;
+}
+
+var indexOf;
+
+if (Array.prototype.indexOf) {
+    indexOf = Array.prototype.indexOf;
+} else {
+    indexOf = function (o) {
+        // I know
+        var i;
+        for (i = 0; i < this.length; ++i) {
+            if (this[i] === o) {
+                return i;
+            }
+        }
+        return -1;
+    };
+}
+
+function daysInMonth(year, month) {
+    if (isNaN(year) || isNaN(month)) {
+        return NaN;
+    }
+    var modMonth = mod(month, 12);
+    year += (month - modMonth) / 12;
+    return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
+}
+
+// FORMATTING
+
+addFormatToken('M', ['MM', 2], 'Mo', function () {
+    return this.month() + 1;
+});
+
+addFormatToken('MMM', 0, 0, function (format) {
+    return this.localeData().monthsShort(this, format);
+});
+
+addFormatToken('MMMM', 0, 0, function (format) {
+    return this.localeData().months(this, format);
+});
+
+// ALIASES
+
+addUnitAlias('month', 'M');
+
+// PRIORITY
+
+addUnitPriority('month', 8);
+
+// PARSING
+
+addRegexToken('M',    match1to2);
+addRegexToken('MM',   match1to2, match2);
+addRegexToken('MMM',  function (isStrict, locale) {
+    return locale.monthsShortRegex(isStrict);
+});
+addRegexToken('MMMM', function (isStrict, locale) {
+    return locale.monthsRegex(isStrict);
+});
+
+addParseToken(['M', 'MM'], function (input, array) {
+    array[MONTH] = toInt(input) - 1;
+});
+
+addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+    var month = config._locale.monthsParse(input, token, config._strict);
+    // if we didn't find a month name, mark the date as invalid.
+    if (month != null) {
+        array[MONTH] = month;
+    } else {
+        getParsingFlags(config).invalidMonth = input;
+    }
+});
+
+// LOCALES
+
+var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
+var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+function localeMonths (m, format) {
+    if (!m) {
+        return isArray(this._months) ? this._months :
+            this._months['standalone'];
+    }
+    return isArray(this._months) ? this._months[m.month()] :
+        this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+function localeMonthsShort (m, format) {
+    if (!m) {
+        return isArray(this._monthsShort) ? this._monthsShort :
+            this._monthsShort['standalone'];
+    }
+    return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
+        this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
+}
+
+function handleStrictParse(monthName, format, strict) {
+    var i, ii, mom, llc = monthName.toLocaleLowerCase();
+    if (!this._monthsParse) {
+        // this is not used
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+        for (i = 0; i < 12; ++i) {
+            mom = createUTC([2000, i]);
+            this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
+            this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'MMM') {
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'MMM') {
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._longMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._longMonthsParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortMonthsParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeMonthsParse (monthName, format, strict) {
+    var i, mom, regex;
+
+    if (this._monthsParseExact) {
+        return handleStrictParse.call(this, monthName, format, strict);
+    }
+
+    if (!this._monthsParse) {
+        this._monthsParse = [];
+        this._longMonthsParse = [];
+        this._shortMonthsParse = [];
+    }
+
+    // TODO: add sorting
+    // Sorting makes sure if one month (or abbr) is a prefix of another
+    // see sorting in computeMonthsParse
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        if (strict && !this._longMonthsParse[i]) {
+            this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
+            this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
+        }
+        if (!strict && !this._monthsParse[i]) {
+            regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+            this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
+            return i;
+        } else if (!strict && this._monthsParse[i].test(monthName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function setMonth (mom, value) {
+    var dayOfMonth;
+
+    if (!mom.isValid()) {
+        // No op
+        return mom;
+    }
+
+    if (typeof value === 'string') {
+        if (/^\d+$/.test(value)) {
+            value = toInt(value);
+        } else {
+            value = mom.localeData().monthsParse(value);
+            // TODO: Another silent failure?
+            if (!isNumber(value)) {
+                return mom;
+            }
+        }
+    }
+
+    dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+    mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+    return mom;
+}
+
+function getSetMonth (value) {
+    if (value != null) {
+        setMonth(this, value);
+        hooks.updateOffset(this, true);
+        return this;
+    } else {
+        return get(this, 'Month');
+    }
+}
+
+function getDaysInMonth () {
+    return daysInMonth(this.year(), this.month());
+}
+
+var defaultMonthsShortRegex = matchWord;
+function monthsShortRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsShortStrictRegex;
+        } else {
+            return this._monthsShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsShortRegex')) {
+            this._monthsShortRegex = defaultMonthsShortRegex;
+        }
+        return this._monthsShortStrictRegex && isStrict ?
+            this._monthsShortStrictRegex : this._monthsShortRegex;
+    }
+}
+
+var defaultMonthsRegex = matchWord;
+function monthsRegex (isStrict) {
+    if (this._monthsParseExact) {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            computeMonthsParse.call(this);
+        }
+        if (isStrict) {
+            return this._monthsStrictRegex;
+        } else {
+            return this._monthsRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_monthsRegex')) {
+            this._monthsRegex = defaultMonthsRegex;
+        }
+        return this._monthsStrictRegex && isStrict ?
+            this._monthsStrictRegex : this._monthsRegex;
+    }
+}
+
+function computeMonthsParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom;
+    for (i = 0; i < 12; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, i]);
+        shortPieces.push(this.monthsShort(mom, ''));
+        longPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.months(mom, ''));
+        mixedPieces.push(this.monthsShort(mom, ''));
+    }
+    // Sorting makes sure if one month (or abbr) is a prefix of another it
+    // will match the longer piece.
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 12; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+    }
+    for (i = 0; i < 24; i++) {
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._monthsShortRegex = this._monthsRegex;
+    this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+}
+
+function createDate (y, m, d, h, M, s, ms) {
+    // can't just apply() to create a date:
+    // https://stackoverflow.com/q/181348
+    var date = new Date(y, m, d, h, M, s, ms);
+
+    // the date constructor remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getFullYear())) {
+        date.setFullYear(y);
+    }
+    return date;
+}
+
+function createUTCDate (y) {
+    var date = new Date(Date.UTC.apply(null, arguments));
+
+    // the Date.UTC function remaps years 0-99 to 1900-1999
+    if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
+        date.setUTCFullYear(y);
+    }
+    return date;
+}
+
+// start-of-first-week - start-of-year
+function firstWeekOffset(year, dow, doy) {
+    var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+        fwd = 7 + dow - doy,
+        // first-week day local weekday -- which local weekday is fwd
+        fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+    return -fwdlw + fwd - 1;
+}
+
+// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+    var localWeekday = (7 + weekday - dow) % 7,
+        weekOffset = firstWeekOffset(year, dow, doy),
+        dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+        resYear, resDayOfYear;
+
+    if (dayOfYear <= 0) {
+        resYear = year - 1;
+        resDayOfYear = daysInYear(resYear) + dayOfYear;
+    } else if (dayOfYear > daysInYear(year)) {
+        resYear = year + 1;
+        resDayOfYear = dayOfYear - daysInYear(year);
+    } else {
+        resYear = year;
+        resDayOfYear = dayOfYear;
+    }
+
+    return {
+        year: resYear,
+        dayOfYear: resDayOfYear
+    };
+}
+
+function weekOfYear(mom, dow, doy) {
+    var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+        week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+        resWeek, resYear;
+
+    if (week < 1) {
+        resYear = mom.year() - 1;
+        resWeek = week + weeksInYear(resYear, dow, doy);
+    } else if (week > weeksInYear(mom.year(), dow, doy)) {
+        resWeek = week - weeksInYear(mom.year(), dow, doy);
+        resYear = mom.year() + 1;
+    } else {
+        resYear = mom.year();
+        resWeek = week;
+    }
+
+    return {
+        week: resWeek,
+        year: resYear
+    };
+}
+
+function weeksInYear(year, dow, doy) {
+    var weekOffset = firstWeekOffset(year, dow, doy),
+        weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+    return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+}
+
+// FORMATTING
+
+addFormatToken('w', ['ww', 2], 'wo', 'week');
+addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+// ALIASES
+
+addUnitAlias('week', 'w');
+addUnitAlias('isoWeek', 'W');
+
+// PRIORITIES
+
+addUnitPriority('week', 5);
+addUnitPriority('isoWeek', 5);
+
+// PARSING
+
+addRegexToken('w',  match1to2);
+addRegexToken('ww', match1to2, match2);
+addRegexToken('W',  match1to2);
+addRegexToken('WW', match1to2, match2);
+
+addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
+    week[token.substr(0, 1)] = toInt(input);
+});
+
+// HELPERS
+
+// LOCALES
+
+function localeWeek (mom) {
+    return weekOfYear(mom, this._week.dow, this._week.doy).week;
+}
+
+var defaultLocaleWeek = {
+    dow : 0, // Sunday is the first day of the week.
+    doy : 6  // The week that contains Jan 1st is the first week of the year.
+};
+
+function localeFirstDayOfWeek () {
+    return this._week.dow;
+}
+
+function localeFirstDayOfYear () {
+    return this._week.doy;
+}
+
+// MOMENTS
+
+function getSetWeek (input) {
+    var week = this.localeData().week(this);
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+function getSetISOWeek (input) {
+    var week = weekOfYear(this, 1, 4).week;
+    return input == null ? week : this.add((input - week) * 7, 'd');
+}
+
+// FORMATTING
+
+addFormatToken('d', 0, 'do', 'day');
+
+addFormatToken('dd', 0, 0, function (format) {
+    return this.localeData().weekdaysMin(this, format);
+});
+
+addFormatToken('ddd', 0, 0, function (format) {
+    return this.localeData().weekdaysShort(this, format);
+});
+
+addFormatToken('dddd', 0, 0, function (format) {
+    return this.localeData().weekdays(this, format);
+});
+
+addFormatToken('e', 0, 0, 'weekday');
+addFormatToken('E', 0, 0, 'isoWeekday');
+
+// ALIASES
+
+addUnitAlias('day', 'd');
+addUnitAlias('weekday', 'e');
+addUnitAlias('isoWeekday', 'E');
+
+// PRIORITY
+addUnitPriority('day', 11);
+addUnitPriority('weekday', 11);
+addUnitPriority('isoWeekday', 11);
+
+// PARSING
+
+addRegexToken('d',    match1to2);
+addRegexToken('e',    match1to2);
+addRegexToken('E',    match1to2);
+addRegexToken('dd',   function (isStrict, locale) {
+    return locale.weekdaysMinRegex(isStrict);
+});
+addRegexToken('ddd',   function (isStrict, locale) {
+    return locale.weekdaysShortRegex(isStrict);
+});
+addRegexToken('dddd',   function (isStrict, locale) {
+    return locale.weekdaysRegex(isStrict);
+});
+
+addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+    var weekday = config._locale.weekdaysParse(input, token, config._strict);
+    // if we didn't get a weekday name, mark the date as invalid
+    if (weekday != null) {
+        week.d = weekday;
+    } else {
+        getParsingFlags(config).invalidWeekday = input;
+    }
+});
+
+addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+    week[token] = toInt(input);
+});
+
+// HELPERS
+
+function parseWeekday(input, locale) {
+    if (typeof input !== 'string') {
+        return input;
+    }
+
+    if (!isNaN(input)) {
+        return parseInt(input, 10);
+    }
+
+    input = locale.weekdaysParse(input);
+    if (typeof input === 'number') {
+        return input;
+    }
+
+    return null;
+}
+
+function parseIsoWeekday(input, locale) {
+    if (typeof input === 'string') {
+        return locale.weekdaysParse(input) % 7 || 7;
+    }
+    return isNaN(input) ? null : input;
+}
+
+// LOCALES
+
+var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+function localeWeekdays (m, format) {
+    if (!m) {
+        return isArray(this._weekdays) ? this._weekdays :
+            this._weekdays['standalone'];
+    }
+    return isArray(this._weekdays) ? this._weekdays[m.day()] :
+        this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()];
+}
+
+var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+function localeWeekdaysShort (m) {
+    return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
+}
+
+var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+function localeWeekdaysMin (m) {
+    return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
+}
+
+function handleStrictParse$1(weekdayName, format, strict) {
+    var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._minWeekdaysParse = [];
+
+        for (i = 0; i < 7; ++i) {
+            mom = createUTC([2000, 1]).day(i);
+            this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
+            this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
+            this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+        }
+    }
+
+    if (strict) {
+        if (format === 'dddd') {
+            ii = indexOf.call(this._weekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    } else {
+        if (format === 'dddd') {
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else if (format === 'ddd') {
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        } else {
+            ii = indexOf.call(this._minWeekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._weekdaysParse, llc);
+            if (ii !== -1) {
+                return ii;
+            }
+            ii = indexOf.call(this._shortWeekdaysParse, llc);
+            return ii !== -1 ? ii : null;
+        }
+    }
+}
+
+function localeWeekdaysParse (weekdayName, format, strict) {
+    var i, mom, regex;
+
+    if (this._weekdaysParseExact) {
+        return handleStrictParse$1.call(this, weekdayName, format, strict);
+    }
+
+    if (!this._weekdaysParse) {
+        this._weekdaysParse = [];
+        this._minWeekdaysParse = [];
+        this._shortWeekdaysParse = [];
+        this._fullWeekdaysParse = [];
+    }
+
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+
+        mom = createUTC([2000, 1]).day(i);
+        if (strict && !this._fullWeekdaysParse[i]) {
+            this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i');
+            this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i');
+            this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i');
+        }
+        if (!this._weekdaysParse[i]) {
+            regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
+            this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+        }
+        // test the regex
+        if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
+            return i;
+        } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+            return i;
+        }
+    }
+}
+
+// MOMENTS
+
+function getSetDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+    if (input != null) {
+        input = parseWeekday(input, this.localeData());
+        return this.add(input - day, 'd');
+    } else {
+        return day;
+    }
+}
+
+function getSetLocaleDayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+    return input == null ? weekday : this.add(input - weekday, 'd');
+}
+
+function getSetISODayOfWeek (input) {
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+
+    // behaves the same as moment#day except
+    // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+    // as a setter, sunday should belong to the previous week.
+
+    if (input != null) {
+        var weekday = parseIsoWeekday(input, this.localeData());
+        return this.day(this.day() % 7 ? weekday : weekday - 7);
+    } else {
+        return this.day() || 7;
+    }
+}
+
+var defaultWeekdaysRegex = matchWord;
+function weekdaysRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysStrictRegex;
+        } else {
+            return this._weekdaysRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            this._weekdaysRegex = defaultWeekdaysRegex;
+        }
+        return this._weekdaysStrictRegex && isStrict ?
+            this._weekdaysStrictRegex : this._weekdaysRegex;
+    }
+}
+
+var defaultWeekdaysShortRegex = matchWord;
+function weekdaysShortRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysShortStrictRegex;
+        } else {
+            return this._weekdaysShortRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+            this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+        }
+        return this._weekdaysShortStrictRegex && isStrict ?
+            this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
+    }
+}
+
+var defaultWeekdaysMinRegex = matchWord;
+function weekdaysMinRegex (isStrict) {
+    if (this._weekdaysParseExact) {
+        if (!hasOwnProp(this, '_weekdaysRegex')) {
+            computeWeekdaysParse.call(this);
+        }
+        if (isStrict) {
+            return this._weekdaysMinStrictRegex;
+        } else {
+            return this._weekdaysMinRegex;
+        }
+    } else {
+        if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+            this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+        }
+        return this._weekdaysMinStrictRegex && isStrict ?
+            this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
+    }
+}
+
+
+function computeWeekdaysParse () {
+    function cmpLenRev(a, b) {
+        return b.length - a.length;
+    }
+
+    var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
+        i, mom, minp, shortp, longp;
+    for (i = 0; i < 7; i++) {
+        // make the regex if we don't have it already
+        mom = createUTC([2000, 1]).day(i);
+        minp = this.weekdaysMin(mom, '');
+        shortp = this.weekdaysShort(mom, '');
+        longp = this.weekdays(mom, '');
+        minPieces.push(minp);
+        shortPieces.push(shortp);
+        longPieces.push(longp);
+        mixedPieces.push(minp);
+        mixedPieces.push(shortp);
+        mixedPieces.push(longp);
+    }
+    // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+    // will match the longer piece.
+    minPieces.sort(cmpLenRev);
+    shortPieces.sort(cmpLenRev);
+    longPieces.sort(cmpLenRev);
+    mixedPieces.sort(cmpLenRev);
+    for (i = 0; i < 7; i++) {
+        shortPieces[i] = regexEscape(shortPieces[i]);
+        longPieces[i] = regexEscape(longPieces[i]);
+        mixedPieces[i] = regexEscape(mixedPieces[i]);
+    }
+
+    this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+    this._weekdaysShortRegex = this._weekdaysRegex;
+    this._weekdaysMinRegex = this._weekdaysRegex;
+
+    this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
+    this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
+    this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
+}
+
+// FORMATTING
+
+function hFormat() {
+    return this.hours() % 12 || 12;
+}
+
+function kFormat() {
+    return this.hours() || 24;
+}
+
+addFormatToken('H', ['HH', 2], 0, 'hour');
+addFormatToken('h', ['hh', 2], 0, hFormat);
+addFormatToken('k', ['kk', 2], 0, kFormat);
+
+addFormatToken('hmm', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('hmmss', 0, 0, function () {
+    return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+addFormatToken('Hmm', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2);
+});
+
+addFormatToken('Hmmss', 0, 0, function () {
+    return '' + this.hours() + zeroFill(this.minutes(), 2) +
+        zeroFill(this.seconds(), 2);
+});
+
+function meridiem (token, lowercase) {
+    addFormatToken(token, 0, 0, function () {
+        return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
+    });
+}
+
+meridiem('a', true);
+meridiem('A', false);
+
+// ALIASES
+
+addUnitAlias('hour', 'h');
+
+// PRIORITY
+addUnitPriority('hour', 13);
+
+// PARSING
+
+function matchMeridiem (isStrict, locale) {
+    return locale._meridiemParse;
+}
+
+addRegexToken('a',  matchMeridiem);
+addRegexToken('A',  matchMeridiem);
+addRegexToken('H',  match1to2);
+addRegexToken('h',  match1to2);
+addRegexToken('k',  match1to2);
+addRegexToken('HH', match1to2, match2);
+addRegexToken('hh', match1to2, match2);
+addRegexToken('kk', match1to2, match2);
+
+addRegexToken('hmm', match3to4);
+addRegexToken('hmmss', match5to6);
+addRegexToken('Hmm', match3to4);
+addRegexToken('Hmmss', match5to6);
+
+addParseToken(['H', 'HH'], HOUR);
+addParseToken(['k', 'kk'], function (input, array, config) {
+    var kInput = toInt(input);
+    array[HOUR] = kInput === 24 ? 0 : kInput;
+});
+addParseToken(['a', 'A'], function (input, array, config) {
+    config._isPm = config._locale.isPM(input);
+    config._meridiem = input;
+});
+addParseToken(['h', 'hh'], function (input, array, config) {
+    array[HOUR] = toInt(input);
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+    getParsingFlags(config).bigHour = true;
+});
+addParseToken('Hmm', function (input, array, config) {
+    var pos = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos));
+    array[MINUTE] = toInt(input.substr(pos));
+});
+addParseToken('Hmmss', function (input, array, config) {
+    var pos1 = input.length - 4;
+    var pos2 = input.length - 2;
+    array[HOUR] = toInt(input.substr(0, pos1));
+    array[MINUTE] = toInt(input.substr(pos1, 2));
+    array[SECOND] = toInt(input.substr(pos2));
+});
+
+// LOCALES
+
+function localeIsPM (input) {
+    // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+    // Using charAt should be more compatible.
+    return ((input + '').toLowerCase().charAt(0) === 'p');
+}
+
+var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
+function localeMeridiem (hours, minutes, isLower) {
+    if (hours > 11) {
+        return isLower ? 'pm' : 'PM';
+    } else {
+        return isLower ? 'am' : 'AM';
+    }
+}
+
+
+// MOMENTS
+
+// Setting the hour should keep the time, because the user explicitly
+// specified which hour he wants. So trying to maintain the same hour (in
+// a new timezone) makes sense. Adding/subtracting hours does not follow
+// this rule.
+var getSetHour = makeGetSet('Hours', true);
+
+// months
+// week
+// weekdays
+// meridiem
+var baseConfig = {
+    calendar: defaultCalendar,
+    longDateFormat: defaultLongDateFormat,
+    invalidDate: defaultInvalidDate,
+    ordinal: defaultOrdinal,
+    dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+    relativeTime: defaultRelativeTime,
+
+    months: defaultLocaleMonths,
+    monthsShort: defaultLocaleMonthsShort,
+
+    week: defaultLocaleWeek,
+
+    weekdays: defaultLocaleWeekdays,
+    weekdaysMin: defaultLocaleWeekdaysMin,
+    weekdaysShort: defaultLocaleWeekdaysShort,
+
+    meridiemParse: defaultLocaleMeridiemParse
+};
+
+// internal storage for locale config files
+var locales = {};
+var localeFamilies = {};
+var globalLocale;
+
+function normalizeLocale(key) {
+    return key ? key.toLowerCase().replace('_', '-') : key;
+}
+
+// pick the locale from the array
+// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+function chooseLocale(names) {
+    var i = 0, j, next, locale, split;
+
+    while (i < names.length) {
+        split = normalizeLocale(names[i]).split('-');
+        j = split.length;
+        next = normalizeLocale(names[i + 1]);
+        next = next ? next.split('-') : null;
+        while (j > 0) {
+            locale = loadLocale(split.slice(0, j).join('-'));
+            if (locale) {
+                return locale;
+            }
+            if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
+                //the next array item is better than a shallower substring of this one
+                break;
+            }
+            j--;
+        }
+        i++;
+    }
+    return null;
+}
+
+function loadLocale(name) {
+    var oldLocale = null;
+    // TODO: Find a better way to register and load all the locales in Node
+    if (!locales[name] && (typeof module !== 'undefined') &&
+            module && module.exports) {
+        try {
+            oldLocale = globalLocale._abbr;
+            var aliasedRequire = require;
+            aliasedRequire('./locale/' + name);
+            getSetGlobalLocale(oldLocale);
+        } catch (e) {}
+    }
+    return locales[name];
+}
+
+// This function will load locale and then set the global locale.  If
+// no arguments are passed in, it will simply return the current global
+// locale key.
+function getSetGlobalLocale (key, values) {
+    var data;
+    if (key) {
+        if (isUndefined(values)) {
+            data = getLocale(key);
+        }
+        else {
+            data = defineLocale(key, values);
+        }
+
+        if (data) {
+            // moment.duration._locale = moment._locale = data;
+            globalLocale = data;
+        }
+    }
+
+    return globalLocale._abbr;
+}
+
+function defineLocale (name, config) {
+    if (config !== null) {
+        var parentConfig = baseConfig;
+        config.abbr = name;
+        if (locales[name] != null) {
+            deprecateSimple('defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                    'an existing locale. moment.defineLocale(localeName, ' +
+                    'config) should only be used for creating a new locale ' +
+                    'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
+            parentConfig = locales[name]._config;
+        } else if (config.parentLocale != null) {
+            if (locales[config.parentLocale] != null) {
+                parentConfig = locales[config.parentLocale]._config;
+            } else {
+                if (!localeFamilies[config.parentLocale]) {
+                    localeFamilies[config.parentLocale] = [];
+                }
+                localeFamilies[config.parentLocale].push({
+                    name: name,
+                    config: config
+                });
+                return null;
+            }
+        }
+        locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+        if (localeFamilies[name]) {
+            localeFamilies[name].forEach(function (x) {
+                defineLocale(x.name, x.config);
+            });
+        }
+
+        // backwards compat for now: also set the locale
+        // make sure we set the locale AFTER all child locales have been
+        // created, so we won't end up with the child locale set.
+        getSetGlobalLocale(name);
+
+
+        return locales[name];
+    } else {
+        // useful for testing
+        delete locales[name];
+        return null;
+    }
+}
+
+function updateLocale(name, config) {
+    if (config != null) {
+        var locale, tmpLocale, parentConfig = baseConfig;
+        // MERGE
+        tmpLocale = loadLocale(name);
+        if (tmpLocale != null) {
+            parentConfig = tmpLocale._config;
+        }
+        config = mergeConfigs(parentConfig, config);
+        locale = new Locale(config);
+        locale.parentLocale = locales[name];
+        locales[name] = locale;
+
+        // backwards compat for now: also set the locale
+        getSetGlobalLocale(name);
+    } else {
+        // pass null for config to unupdate, useful for tests
+        if (locales[name] != null) {
+            if (locales[name].parentLocale != null) {
+                locales[name] = locales[name].parentLocale;
+            } else if (locales[name] != null) {
+                delete locales[name];
+            }
+        }
+    }
+    return locales[name];
+}
+
+// returns locale data
+function getLocale (key) {
+    var locale;
+
+    if (key && key._locale && key._locale._abbr) {
+        key = key._locale._abbr;
+    }
+
+    if (!key) {
+        return globalLocale;
+    }
+
+    if (!isArray(key)) {
+        //short-circuit everything else
+        locale = loadLocale(key);
+        if (locale) {
+            return locale;
+        }
+        key = [key];
+    }
+
+    return chooseLocale(key);
+}
+
+function listLocales() {
+    return keys(locales);
+}
+
+function checkOverflow (m) {
+    var overflow;
+    var a = m._a;
+
+    if (a && getParsingFlags(m).overflow === -2) {
+        overflow =
+            a[MONTH]       < 0 || a[MONTH]       > 11  ? MONTH :
+            a[DATE]        < 1 || a[DATE]        > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
+            a[HOUR]        < 0 || a[HOUR]        > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
+            a[MINUTE]      < 0 || a[MINUTE]      > 59  ? MINUTE :
+            a[SECOND]      < 0 || a[SECOND]      > 59  ? SECOND :
+            a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
+            -1;
+
+        if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
+            overflow = DATE;
+        }
+        if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+            overflow = WEEK;
+        }
+        if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+            overflow = WEEKDAY;
+        }
+
+        getParsingFlags(m).overflow = overflow;
+    }
+
+    return m;
+}
+
+// Pick the first defined of two or three arguments.
+function defaults(a, b, c) {
+    if (a != null) {
+        return a;
+    }
+    if (b != null) {
+        return b;
+    }
+    return c;
+}
+
+function currentDateArray(config) {
+    // hooks is actually the exported moment object
+    var nowValue = new Date(hooks.now());
+    if (config._useUTC) {
+        return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
+    }
+    return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+}
+
+// convert an array to a date.
+// the array should mirror the parameters below
+// note: all values past the year are optional and will default to the lowest possible value.
+// [year, month, day , hour, minute, second, millisecond]
+function configFromArray (config) {
+    var i, date, input = [], currentDate, expectedWeekday, yearToUse;
+
+    if (config._d) {
+        return;
+    }
+
+    currentDate = currentDateArray(config);
+
+    //compute day of the year from weeks and weekdays
+    if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+        dayOfYearFromWeekInfo(config);
+    }
+
+    //if the day of the year is set, figure out what it is
+    if (config._dayOfYear != null) {
+        yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+        if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
+            getParsingFlags(config)._overflowDayOfYear = true;
+        }
+
+        date = createUTCDate(yearToUse, 0, config._dayOfYear);
+        config._a[MONTH] = date.getUTCMonth();
+        config._a[DATE] = date.getUTCDate();
+    }
+
+    // Default to current date.
+    // * if no year, month, day of month are given, default to today
+    // * if day of month is given, default month and year
+    // * if month is given, default only year
+    // * if year is given, don't default anything
+    for (i = 0; i < 3 && config._a[i] == null; ++i) {
+        config._a[i] = input[i] = currentDate[i];
+    }
+
+    // Zero out whatever was not defaulted, including time
+    for (; i < 7; i++) {
+        config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
+    }
+
+    // Check for 24:00:00.000
+    if (config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0) {
+        config._nextDay = true;
+        config._a[HOUR] = 0;
+    }
+
+    config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
+    expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
+
+    // Apply timezone offset from input. The actual utcOffset can be changed
+    // with parseZone.
+    if (config._tzm != null) {
+        config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+    }
+
+    if (config._nextDay) {
+        config._a[HOUR] = 24;
+    }
+
+    // check for mismatching day of week
+    if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
+        getParsingFlags(config).weekdayMismatch = true;
+    }
+}
+
+function dayOfYearFromWeekInfo(config) {
+    var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
+
+    w = config._w;
+    if (w.GG != null || w.W != null || w.E != null) {
+        dow = 1;
+        doy = 4;
+
+        // TODO: We need to take the current isoWeekYear, but that depends on
+        // how we interpret now (local, utc, fixed offset). So create
+        // a now version of current config (take local/utc/offset flags, and
+        // create now).
+        weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
+        week = defaults(w.W, 1);
+        weekday = defaults(w.E, 1);
+        if (weekday < 1 || weekday > 7) {
+            weekdayOverflow = true;
+        }
+    } else {
+        dow = config._locale._week.dow;
+        doy = config._locale._week.doy;
+
+        var curWeek = weekOfYear(createLocal(), dow, doy);
+
+        weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+        // Default to current week.
+        week = defaults(w.w, curWeek.week);
+
+        if (w.d != null) {
+            // weekday -- low day numbers are considered next week
+            weekday = w.d;
+            if (weekday < 0 || weekday > 6) {
+                weekdayOverflow = true;
+            }
+        } else if (w.e != null) {
+            // local weekday -- counting starts from begining of week
+            weekday = w.e + dow;
+            if (w.e < 0 || w.e > 6) {
+                weekdayOverflow = true;
+            }
+        } else {
+            // default to begining of week
+            weekday = dow;
+        }
+    }
+    if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+        getParsingFlags(config)._overflowWeeks = true;
+    } else if (weekdayOverflow != null) {
+        getParsingFlags(config)._overflowWeekday = true;
+    } else {
+        temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+        config._a[YEAR] = temp.year;
+        config._dayOfYear = temp.dayOfYear;
+    }
+}
+
+// iso 8601 regex
+// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
+
+var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
+
+var isoDates = [
+    ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+    ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+    ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+    ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+    ['YYYY-DDD', /\d{4}-\d{3}/],
+    ['YYYY-MM', /\d{4}-\d\d/, false],
+    ['YYYYYYMMDD', /[+-]\d{10}/],
+    ['YYYYMMDD', /\d{8}/],
+    // YYYYMM is NOT allowed by the standard
+    ['GGGG[W]WWE', /\d{4}W\d{3}/],
+    ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+    ['YYYYDDD', /\d{7}/]
+];
+
+// iso time formats and regexes
+var isoTimes = [
+    ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+    ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+    ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+    ['HH:mm', /\d\d:\d\d/],
+    ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+    ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+    ['HHmmss', /\d\d\d\d\d\d/],
+    ['HHmm', /\d\d\d\d/],
+    ['HH', /\d\d/]
+];
+
+var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
+
+// date from iso format
+function configFromISO(config) {
+    var i, l,
+        string = config._i,
+        match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+        allowTime, dateFormat, timeFormat, tzFormat;
+
+    if (match) {
+        getParsingFlags(config).iso = true;
+
+        for (i = 0, l = isoDates.length; i < l; i++) {
+            if (isoDates[i][1].exec(match[1])) {
+                dateFormat = isoDates[i][0];
+                allowTime = isoDates[i][2] !== false;
+                break;
+            }
+        }
+        if (dateFormat == null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[3]) {
+            for (i = 0, l = isoTimes.length; i < l; i++) {
+                if (isoTimes[i][1].exec(match[3])) {
+                    // match[2] should be 'T' or space
+                    timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                    break;
+                }
+            }
+            if (timeFormat == null) {
+                config._isValid = false;
+                return;
+            }
+        }
+        if (!allowTime && timeFormat != null) {
+            config._isValid = false;
+            return;
+        }
+        if (match[4]) {
+            if (tzRegex.exec(match[4])) {
+                tzFormat = 'Z';
+            } else {
+                config._isValid = false;
+                return;
+            }
+        }
+        config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+        configFromStringAndFormat(config);
+    } else {
+        config._isValid = false;
+    }
+}
+
+// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
+
+function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
+    var result = [
+        untruncateYear(yearStr),
+        defaultLocaleMonthsShort.indexOf(monthStr),
+        parseInt(dayStr, 10),
+        parseInt(hourStr, 10),
+        parseInt(minuteStr, 10)
+    ];
+
+    if (secondStr) {
+        result.push(parseInt(secondStr, 10));
+    }
+
+    return result;
+}
+
+function untruncateYear(yearStr) {
+    var year = parseInt(yearStr, 10);
+    if (year <= 49) {
+        return 2000 + year;
+    } else if (year <= 999) {
+        return 1900 + year;
+    }
+    return year;
+}
+
+function preprocessRFC2822(s) {
+    // Remove comments and folding whitespace and replace multiple-spaces with a single space
+    return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
+}
+
+function checkWeekday(weekdayStr, parsedInput, config) {
+    if (weekdayStr) {
+        // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
+        var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+            weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
+        if (weekdayProvided !== weekdayActual) {
+            getParsingFlags(config).weekdayMismatch = true;
+            config._isValid = false;
+            return false;
+        }
+    }
+    return true;
+}
+
+var obsOffsets = {
+    UT: 0,
+    GMT: 0,
+    EDT: -4 * 60,
+    EST: -5 * 60,
+    CDT: -5 * 60,
+    CST: -6 * 60,
+    MDT: -6 * 60,
+    MST: -7 * 60,
+    PDT: -7 * 60,
+    PST: -8 * 60
+};
+
+function calculateOffset(obsOffset, militaryOffset, numOffset) {
+    if (obsOffset) {
+        return obsOffsets[obsOffset];
+    } else if (militaryOffset) {
+        // the only allowed military tz is Z
+        return 0;
+    } else {
+        var hm = parseInt(numOffset, 10);
+        var m = hm % 100, h = (hm - m) / 100;
+        return h * 60 + m;
+    }
+}
+
+// date and time from ref 2822 format
+function configFromRFC2822(config) {
+    var match = rfc2822.exec(preprocessRFC2822(config._i));
+    if (match) {
+        var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
+        if (!checkWeekday(match[1], parsedArray, config)) {
+            return;
+        }
+
+        config._a = parsedArray;
+        config._tzm = calculateOffset(match[8], match[9], match[10]);
+
+        config._d = createUTCDate.apply(null, config._a);
+        config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+
+        getParsingFlags(config).rfc2822 = true;
+    } else {
+        config._isValid = false;
+    }
+}
+
+// date from iso format or fallback
+function configFromString(config) {
+    var matched = aspNetJsonRegex.exec(config._i);
+
+    if (matched !== null) {
+        config._d = new Date(+matched[1]);
+        return;
+    }
+
+    configFromISO(config);
+    if (config._isValid === false) {
+        delete config._isValid;
+    } else {
+        return;
+    }
+
+    configFromRFC2822(config);
+    if (config._isValid === false) {
+        delete config._isValid;
+    } else {
+        return;
+    }
+
+    // Final attempt, use Input Fallback
+    hooks.createFromInputFallback(config);
+}
+
+hooks.createFromInputFallback = deprecate(
+    'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+    'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+    'discouraged and will be removed in an upcoming major release. Please refer to ' +
+    'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+    function (config) {
+        config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+    }
+);
+
+// constant that refers to the ISO standard
+hooks.ISO_8601 = function () {};
+
+// constant that refers to the RFC 2822 form
+hooks.RFC_2822 = function () {};
+
+// date from string and format string
+function configFromStringAndFormat(config) {
+    // TODO: Move this to another part of the creation flow to prevent circular deps
+    if (config._f === hooks.ISO_8601) {
+        configFromISO(config);
+        return;
+    }
+    if (config._f === hooks.RFC_2822) {
+        configFromRFC2822(config);
+        return;
+    }
+    config._a = [];
+    getParsingFlags(config).empty = true;
+
+    // This array is used to make a Date, either with `new Date` or `Date.UTC`
+    var string = '' + config._i,
+        i, parsedInput, tokens, token, skipped,
+        stringLength = string.length,
+        totalParsedInputLength = 0;
+
+    tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+    for (i = 0; i < tokens.length; i++) {
+        token = tokens[i];
+        parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
+        // console.log('token', token, 'parsedInput', parsedInput,
+        //         'regex', getParseRegexForToken(token, config));
+        if (parsedInput) {
+            skipped = string.substr(0, string.indexOf(parsedInput));
+            if (skipped.length > 0) {
+                getParsingFlags(config).unusedInput.push(skipped);
+            }
+            string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
+            totalParsedInputLength += parsedInput.length;
+        }
+        // don't parse if it's not a known token
+        if (formatTokenFunctions[token]) {
+            if (parsedInput) {
+                getParsingFlags(config).empty = false;
+            }
+            else {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+            addTimeToArrayFromToken(token, parsedInput, config);
+        }
+        else if (config._strict && !parsedInput) {
+            getParsingFlags(config).unusedTokens.push(token);
+        }
+    }
+
+    // add remaining unparsed input length to the string
+    getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
+    if (string.length > 0) {
+        getParsingFlags(config).unusedInput.push(string);
+    }
+
+    // clear _12h flag if hour is <= 12
+    if (config._a[HOUR] <= 12 &&
+        getParsingFlags(config).bigHour === true &&
+        config._a[HOUR] > 0) {
+        getParsingFlags(config).bigHour = undefined;
+    }
+
+    getParsingFlags(config).parsedDateParts = config._a.slice(0);
+    getParsingFlags(config).meridiem = config._meridiem;
+    // handle meridiem
+    config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
+
+    configFromArray(config);
+    checkOverflow(config);
+}
+
+
+function meridiemFixWrap (locale, hour, meridiem) {
+    var isPm;
+
+    if (meridiem == null) {
+        // nothing to do
+        return hour;
+    }
+    if (locale.meridiemHour != null) {
+        return locale.meridiemHour(hour, meridiem);
+    } else if (locale.isPM != null) {
+        // Fallback
+        isPm = locale.isPM(meridiem);
+        if (isPm && hour < 12) {
+            hour += 12;
+        }
+        if (!isPm && hour === 12) {
+            hour = 0;
+        }
+        return hour;
+    } else {
+        // this is not supposed to happen
+        return hour;
+    }
+}
+
+// date from string and array of format strings
+function configFromStringAndArray(config) {
+    var tempConfig,
+        bestMoment,
+
+        scoreToBeat,
+        i,
+        currentScore;
+
+    if (config._f.length === 0) {
+        getParsingFlags(config).invalidFormat = true;
+        config._d = new Date(NaN);
+        return;
+    }
+
+    for (i = 0; i < config._f.length; i++) {
+        currentScore = 0;
+        tempConfig = copyConfig({}, config);
+        if (config._useUTC != null) {
+            tempConfig._useUTC = config._useUTC;
+        }
+        tempConfig._f = config._f[i];
+        configFromStringAndFormat(tempConfig);
+
+        if (!isValid(tempConfig)) {
+            continue;
+        }
+
+        // if there is any input that was not parsed add a penalty for that format
+        currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+        //or tokens
+        currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+        getParsingFlags(tempConfig).score = currentScore;
+
+        if (scoreToBeat == null || currentScore < scoreToBeat) {
+            scoreToBeat = currentScore;
+            bestMoment = tempConfig;
+        }
+    }
+
+    extend(config, bestMoment || tempConfig);
+}
+
+function configFromObject(config) {
+    if (config._d) {
+        return;
+    }
+
+    var i = normalizeObjectUnits(config._i);
+    config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
+        return obj && parseInt(obj, 10);
+    });
+
+    configFromArray(config);
+}
+
+function createFromConfig (config) {
+    var res = new Moment(checkOverflow(prepareConfig(config)));
+    if (res._nextDay) {
+        // Adding is smart enough around DST
+        res.add(1, 'd');
+        res._nextDay = undefined;
+    }
+
+    return res;
+}
+
+function prepareConfig (config) {
+    var input = config._i,
+        format = config._f;
+
+    config._locale = config._locale || getLocale(config._l);
+
+    if (input === null || (format === undefined && input === '')) {
+        return createInvalid({nullInput: true});
+    }
+
+    if (typeof input === 'string') {
+        config._i = input = config._locale.preparse(input);
+    }
+
+    if (isMoment(input)) {
+        return new Moment(checkOverflow(input));
+    } else if (isDate(input)) {
+        config._d = input;
+    } else if (isArray(format)) {
+        configFromStringAndArray(config);
+    } else if (format) {
+        configFromStringAndFormat(config);
+    }  else {
+        configFromInput(config);
+    }
+
+    if (!isValid(config)) {
+        config._d = null;
+    }
+
+    return config;
+}
+
+function configFromInput(config) {
+    var input = config._i;
+    if (isUndefined(input)) {
+        config._d = new Date(hooks.now());
+    } else if (isDate(input)) {
+        config._d = new Date(input.valueOf());
+    } else if (typeof input === 'string') {
+        configFromString(config);
+    } else if (isArray(input)) {
+        config._a = map(input.slice(0), function (obj) {
+            return parseInt(obj, 10);
+        });
+        configFromArray(config);
+    } else if (isObject(input)) {
+        configFromObject(config);
+    } else if (isNumber(input)) {
+        // from milliseconds
+        config._d = new Date(input);
+    } else {
+        hooks.createFromInputFallback(config);
+    }
+}
+
+function createLocalOrUTC (input, format, locale, strict, isUTC) {
+    var c = {};
+
+    if (locale === true || locale === false) {
+        strict = locale;
+        locale = undefined;
+    }
+
+    if ((isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)) {
+        input = undefined;
+    }
+    // object construction must be done this way.
+    // https://github.com/moment/moment/issues/1423
+    c._isAMomentObject = true;
+    c._useUTC = c._isUTC = isUTC;
+    c._l = locale;
+    c._i = input;
+    c._f = format;
+    c._strict = strict;
+
+    return createFromConfig(c);
+}
+
+function createLocal (input, format, locale, strict) {
+    return createLocalOrUTC(input, format, locale, strict, false);
+}
+
+var prototypeMin = deprecate(
+    'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other < this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+var prototypeMax = deprecate(
+    'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+    function () {
+        var other = createLocal.apply(null, arguments);
+        if (this.isValid() && other.isValid()) {
+            return other > this ? this : other;
+        } else {
+            return createInvalid();
+        }
+    }
+);
+
+// Pick a moment m from moments so that m[fn](other) is true for all
+// other. This relies on the function fn to be transitive.
+//
+// moments should either be an array of moment objects or an array, whose
+// first element is an array of moment objects.
+function pickBy(fn, moments) {
+    var res, i;
+    if (moments.length === 1 && isArray(moments[0])) {
+        moments = moments[0];
+    }
+    if (!moments.length) {
+        return createLocal();
+    }
+    res = moments[0];
+    for (i = 1; i < moments.length; ++i) {
+        if (!moments[i].isValid() || moments[i][fn](res)) {
+            res = moments[i];
+        }
+    }
+    return res;
+}
+
+// TODO: Use [].sort instead?
+function min () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isBefore', args);
+}
+
+function max () {
+    var args = [].slice.call(arguments, 0);
+
+    return pickBy('isAfter', args);
+}
+
+var now = function () {
+    return Date.now ? Date.now() : +(new Date());
+};
+
+var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
+
+function isDurationValid(m) {
+    for (var key in m) {
+        if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
+            return false;
+        }
+    }
+
+    var unitHasDecimal = false;
+    for (var i = 0; i < ordering.length; ++i) {
+        if (m[ordering[i]]) {
+            if (unitHasDecimal) {
+                return false; // only allow non-integers for smallest unit
+            }
+            if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+                unitHasDecimal = true;
+            }
+        }
+    }
+
+    return true;
+}
+
+function isValid$1() {
+    return this._isValid;
+}
+
+function createInvalid$1() {
+    return createDuration(NaN);
+}
+
+function Duration (duration) {
+    var normalizedInput = normalizeObjectUnits(duration),
+        years = normalizedInput.year || 0,
+        quarters = normalizedInput.quarter || 0,
+        months = normalizedInput.month || 0,
+        weeks = normalizedInput.week || 0,
+        days = normalizedInput.day || 0,
+        hours = normalizedInput.hour || 0,
+        minutes = normalizedInput.minute || 0,
+        seconds = normalizedInput.second || 0,
+        milliseconds = normalizedInput.millisecond || 0;
+
+    this._isValid = isDurationValid(normalizedInput);
+
+    // representation for dateAddRemove
+    this._milliseconds = +milliseconds +
+        seconds * 1e3 + // 1000
+        minutes * 6e4 + // 1000 * 60
+        hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+    // Because of dateAddRemove treats 24 hours as different from a
+    // day when working around DST, we need to store them separately
+    this._days = +days +
+        weeks * 7;
+    // It is impossible to translate months into days without knowing
+    // which months you are are talking about, so we have to store
+    // it separately.
+    this._months = +months +
+        quarters * 3 +
+        years * 12;
+
+    this._data = {};
+
+    this._locale = getLocale();
+
+    this._bubble();
+}
+
+function isDuration (obj) {
+    return obj instanceof Duration;
+}
+
+function absRound (number) {
+    if (number < 0) {
+        return Math.round(-1 * number) * -1;
+    } else {
+        return Math.round(number);
+    }
+}
+
+// FORMATTING
+
+function offset (token, separator) {
+    addFormatToken(token, 0, 0, function () {
+        var offset = this.utcOffset();
+        var sign = '+';
+        if (offset < 0) {
+            offset = -offset;
+            sign = '-';
+        }
+        return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
+    });
+}
+
+offset('Z', ':');
+offset('ZZ', '');
+
+// PARSING
+
+addRegexToken('Z',  matchShortOffset);
+addRegexToken('ZZ', matchShortOffset);
+addParseToken(['Z', 'ZZ'], function (input, array, config) {
+    config._useUTC = true;
+    config._tzm = offsetFromString(matchShortOffset, input);
+});
+
+// HELPERS
+
+// timezone chunker
+// '+10:00' > ['10',  '00']
+// '-1530'  > ['-15', '30']
+var chunkOffset = /([\+\-]|\d\d)/gi;
+
+function offsetFromString(matcher, string) {
+    var matches = (string || '').match(matcher);
+
+    if (matches === null) {
+        return null;
+    }
+
+    var chunk   = matches[matches.length - 1] || [];
+    var parts   = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+    var minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+    return minutes === 0 ?
+      0 :
+      parts[0] === '+' ? minutes : -minutes;
+}
+
+// Return a moment from input, that is local/utc/zone equivalent to model.
+function cloneWithOffset(input, model) {
+    var res, diff;
+    if (model._isUTC) {
+        res = model.clone();
+        diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
+        // Use low-level api, because this fn is low-level api.
+        res._d.setTime(res._d.valueOf() + diff);
+        hooks.updateOffset(res, false);
+        return res;
+    } else {
+        return createLocal(input).local();
+    }
+}
+
+function getDateOffset (m) {
+    // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+    // https://github.com/moment/moment/pull/1871
+    return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
+}
+
+// HOOKS
+
+// This function will be called whenever a moment is mutated.
+// It is intended to keep the offset in sync with the timezone.
+hooks.updateOffset = function () {};
+
+// MOMENTS
+
+// keepLocalTime = true means only change the timezone, without
+// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+// +0200, so we adjust the time as needed, to be valid.
+//
+// Keeping the time actually adds/subtracts (one hour)
+// from the actual represented time. That is why we call updateOffset
+// a second time. In case it wants us to change the offset again
+// _changeInProgress == true case, then we have to adjust, because
+// there is no such time in the given timezone.
+function getSetOffset (input, keepLocalTime, keepMinutes) {
+    var offset = this._offset || 0,
+        localAdjust;
+    if (!this.isValid()) {
+        return input != null ? this : NaN;
+    }
+    if (input != null) {
+        if (typeof input === 'string') {
+            input = offsetFromString(matchShortOffset, input);
+            if (input === null) {
+                return this;
+            }
+        } else if (Math.abs(input) < 16 && !keepMinutes) {
+            input = input * 60;
+        }
+        if (!this._isUTC && keepLocalTime) {
+            localAdjust = getDateOffset(this);
+        }
+        this._offset = input;
+        this._isUTC = true;
+        if (localAdjust != null) {
+            this.add(localAdjust, 'm');
+        }
+        if (offset !== input) {
+            if (!keepLocalTime || this._changeInProgress) {
+                addSubtract(this, createDuration(input - offset, 'm'), 1, false);
+            } else if (!this._changeInProgress) {
+                this._changeInProgress = true;
+                hooks.updateOffset(this, true);
+                this._changeInProgress = null;
+            }
+        }
+        return this;
+    } else {
+        return this._isUTC ? offset : getDateOffset(this);
+    }
+}
+
+function getSetZone (input, keepLocalTime) {
+    if (input != null) {
+        if (typeof input !== 'string') {
+            input = -input;
+        }
+
+        this.utcOffset(input, keepLocalTime);
+
+        return this;
+    } else {
+        return -this.utcOffset();
+    }
+}
+
+function setOffsetToUTC (keepLocalTime) {
+    return this.utcOffset(0, keepLocalTime);
+}
+
+function setOffsetToLocal (keepLocalTime) {
+    if (this._isUTC) {
+        this.utcOffset(0, keepLocalTime);
+        this._isUTC = false;
+
+        if (keepLocalTime) {
+            this.subtract(getDateOffset(this), 'm');
+        }
+    }
+    return this;
+}
+
+function setOffsetToParsedOffset () {
+    if (this._tzm != null) {
+        this.utcOffset(this._tzm, false, true);
+    } else if (typeof this._i === 'string') {
+        var tZone = offsetFromString(matchOffset, this._i);
+        if (tZone != null) {
+            this.utcOffset(tZone);
+        }
+        else {
+            this.utcOffset(0, true);
+        }
+    }
+    return this;
+}
+
+function hasAlignedHourOffset (input) {
+    if (!this.isValid()) {
+        return false;
+    }
+    input = input ? createLocal(input).utcOffset() : 0;
+
+    return (this.utcOffset() - input) % 60 === 0;
+}
+
+function isDaylightSavingTime () {
+    return (
+        this.utcOffset() > this.clone().month(0).utcOffset() ||
+        this.utcOffset() > this.clone().month(5).utcOffset()
+    );
+}
+
+function isDaylightSavingTimeShifted () {
+    if (!isUndefined(this._isDSTShifted)) {
+        return this._isDSTShifted;
+    }
+
+    var c = {};
+
+    copyConfig(c, this);
+    c = prepareConfig(c);
+
+    if (c._a) {
+        var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+        this._isDSTShifted = this.isValid() &&
+            compareArrays(c._a, other.toArray()) > 0;
+    } else {
+        this._isDSTShifted = false;
+    }
+
+    return this._isDSTShifted;
+}
+
+function isLocal () {
+    return this.isValid() ? !this._isUTC : false;
+}
+
+function isUtcOffset () {
+    return this.isValid() ? this._isUTC : false;
+}
+
+function isUtc () {
+    return this.isValid() ? this._isUTC && this._offset === 0 : false;
+}
+
+// ASP.NET json date format regex
+var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
+
+// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+// and further modified to allow for strings containing both week and day
+var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+function createDuration (input, key) {
+    var duration = input,
+        // matching against regexp is expensive, do it on demand
+        match = null,
+        sign,
+        ret,
+        diffRes;
+
+    if (isDuration(input)) {
+        duration = {
+            ms : input._milliseconds,
+            d  : input._days,
+            M  : input._months
+        };
+    } else if (isNumber(input)) {
+        duration = {};
+        if (key) {
+            duration[key] = input;
+        } else {
+            duration.milliseconds = input;
+        }
+    } else if (!!(match = aspNetRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : 1;
+        duration = {
+            y  : 0,
+            d  : toInt(match[DATE])                         * sign,
+            h  : toInt(match[HOUR])                         * sign,
+            m  : toInt(match[MINUTE])                       * sign,
+            s  : toInt(match[SECOND])                       * sign,
+            ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
+        };
+    } else if (!!(match = isoRegex.exec(input))) {
+        sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1;
+        duration = {
+            y : parseIso(match[2], sign),
+            M : parseIso(match[3], sign),
+            w : parseIso(match[4], sign),
+            d : parseIso(match[5], sign),
+            h : parseIso(match[6], sign),
+            m : parseIso(match[7], sign),
+            s : parseIso(match[8], sign)
+        };
+    } else if (duration == null) {// checks for null or undefined
+        duration = {};
+    } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
+        diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
+
+        duration = {};
+        duration.ms = diffRes.milliseconds;
+        duration.M = diffRes.months;
+    }
+
+    ret = new Duration(duration);
+
+    if (isDuration(input) && hasOwnProp(input, '_locale')) {
+        ret._locale = input._locale;
+    }
+
+    return ret;
+}
+
+createDuration.fn = Duration.prototype;
+createDuration.invalid = createInvalid$1;
+
+function parseIso (inp, sign) {
+    // We'd normally use ~~inp for this, but unfortunately it also
+    // converts floats to ints.
+    // inp may be undefined, so careful calling replace on it.
+    var res = inp && parseFloat(inp.replace(',', '.'));
+    // apply sign while we're at it
+    return (isNaN(res) ? 0 : res) * sign;
+}
+
+function positiveMomentsDifference(base, other) {
+    var res = {milliseconds: 0, months: 0};
+
+    res.months = other.month() - base.month() +
+        (other.year() - base.year()) * 12;
+    if (base.clone().add(res.months, 'M').isAfter(other)) {
+        --res.months;
+    }
+
+    res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
+
+    return res;
+}
+
+function momentsDifference(base, other) {
+    var res;
+    if (!(base.isValid() && other.isValid())) {
+        return {milliseconds: 0, months: 0};
+    }
+
+    other = cloneWithOffset(other, base);
+    if (base.isBefore(other)) {
+        res = positiveMomentsDifference(base, other);
+    } else {
+        res = positiveMomentsDifference(other, base);
+        res.milliseconds = -res.milliseconds;
+        res.months = -res.months;
+    }
+
+    return res;
+}
+
+// TODO: remove 'name' arg after deprecation is removed
+function createAdder(direction, name) {
+    return function (val, period) {
+        var dur, tmp;
+        //invert the arguments, but complain about it
+        if (period !== null && !isNaN(+period)) {
+            deprecateSimple(name, 'moment().' + name  + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
+            'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
+            tmp = val; val = period; period = tmp;
+        }
+
+        val = typeof val === 'string' ? +val : val;
+        dur = createDuration(val, period);
+        addSubtract(this, dur, direction);
+        return this;
+    };
+}
+
+function addSubtract (mom, duration, isAdding, updateOffset) {
+    var milliseconds = duration._milliseconds,
+        days = absRound(duration._days),
+        months = absRound(duration._months);
+
+    if (!mom.isValid()) {
+        // No op
+        return;
+    }
+
+    updateOffset = updateOffset == null ? true : updateOffset;
+
+    if (months) {
+        setMonth(mom, get(mom, 'Month') + months * isAdding);
+    }
+    if (days) {
+        set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
+    }
+    if (milliseconds) {
+        mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+    }
+    if (updateOffset) {
+        hooks.updateOffset(mom, days || months);
+    }
+}
+
+var add      = createAdder(1, 'add');
+var subtract = createAdder(-1, 'subtract');
+
+function getCalendarFormat(myMoment, now) {
+    var diff = myMoment.diff(now, 'days', true);
+    return diff < -6 ? 'sameElse' :
+            diff < -1 ? 'lastWeek' :
+            diff < 0 ? 'lastDay' :
+            diff < 1 ? 'sameDay' :
+            diff < 2 ? 'nextDay' :
+            diff < 7 ? 'nextWeek' : 'sameElse';
+}
+
+function calendar$1 (time, formats) {
+    // We want to compare the start of today, vs this.
+    // Getting start-of-today depends on whether we're local/utc/offset or not.
+    var now = time || createLocal(),
+        sod = cloneWithOffset(now, this).startOf('day'),
+        format = hooks.calendarFormat(this, sod) || 'sameElse';
+
+    var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
+
+    return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
+}
+
+function clone () {
+    return new Moment(this);
+}
+
+function isAfter (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() > localInput.valueOf();
+    } else {
+        return localInput.valueOf() < this.clone().startOf(units).valueOf();
+    }
+}
+
+function isBefore (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input);
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(!isUndefined(units) ? units : 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() < localInput.valueOf();
+    } else {
+        return this.clone().endOf(units).valueOf() < localInput.valueOf();
+    }
+}
+
+function isBetween (from, to, units, inclusivity) {
+    inclusivity = inclusivity || '()';
+    return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) &&
+        (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units));
+}
+
+function isSame (input, units) {
+    var localInput = isMoment(input) ? input : createLocal(input),
+        inputMs;
+    if (!(this.isValid() && localInput.isValid())) {
+        return false;
+    }
+    units = normalizeUnits(units || 'millisecond');
+    if (units === 'millisecond') {
+        return this.valueOf() === localInput.valueOf();
+    } else {
+        inputMs = localInput.valueOf();
+        return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
+    }
+}
+
+function isSameOrAfter (input, units) {
+    return this.isSame(input, units) || this.isAfter(input,units);
+}
+
+function isSameOrBefore (input, units) {
+    return this.isSame(input, units) || this.isBefore(input,units);
+}
+
+function diff (input, units, asFloat) {
+    var that,
+        zoneDelta,
+        delta, output;
+
+    if (!this.isValid()) {
+        return NaN;
+    }
+
+    that = cloneWithOffset(input, this);
+
+    if (!that.isValid()) {
+        return NaN;
+    }
+
+    zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+    units = normalizeUnits(units);
+
+    switch (units) {
+        case 'year': output = monthDiff(this, that) / 12; break;
+        case 'month': output = monthDiff(this, that); break;
+        case 'quarter': output = monthDiff(this, that) / 3; break;
+        case 'second': output = (this - that) / 1e3; break; // 1000
+        case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
+        case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
+        case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
+        case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
+        default: output = this - that;
+    }
+
+    return asFloat ? output : absFloor(output);
+}
+
+function monthDiff (a, b) {
+    // difference in months
+    var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
+        // b is in (anchor - 1 month, anchor + 1 month)
+        anchor = a.clone().add(wholeMonthDiff, 'months'),
+        anchor2, adjust;
+
+    if (b - anchor < 0) {
+        anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor - anchor2);
+    } else {
+        anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+        // linear across the month
+        adjust = (b - anchor) / (anchor2 - anchor);
+    }
+
+    //check for negative zero, return zero if negative zero
+    return -(wholeMonthDiff + adjust) || 0;
+}
+
+hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+function toString () {
+    return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+}
+
+function toISOString(keepOffset) {
+    if (!this.isValid()) {
+        return null;
+    }
+    var utc = keepOffset !== true;
+    var m = utc ? this.clone().utc() : this;
+    if (m.year() < 0 || m.year() > 9999) {
+        return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
+    }
+    if (isFunction(Date.prototype.toISOString)) {
+        // native implementation is ~50x faster, use it when we can
+        if (utc) {
+            return this.toDate().toISOString();
+        } else {
+            return new Date(this._d.valueOf()).toISOString().replace('Z', formatMoment(m, 'Z'));
+        }
+    }
+    return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
+}
+
+/**
+ * Return a human readable representation of a moment that can
+ * also be evaluated to get a new moment which is the same
+ *
+ * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+ */
+function inspect () {
+    if (!this.isValid()) {
+        return 'moment.invalid(/* ' + this._i + ' */)';
+    }
+    var func = 'moment';
+    var zone = '';
+    if (!this.isLocal()) {
+        func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+        zone = 'Z';
+    }
+    var prefix = '[' + func + '("]';
+    var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
+    var datetime = '-MM-DD[T]HH:mm:ss.SSS';
+    var suffix = zone + '[")]';
+
+    return this.format(prefix + year + datetime + suffix);
+}
+
+function format (inputString) {
+    if (!inputString) {
+        inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
+    }
+    var output = formatMoment(this, inputString);
+    return this.localeData().postformat(output);
+}
+
+function from (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function fromNow (withoutSuffix) {
+    return this.from(createLocal(), withoutSuffix);
+}
+
+function to (time, withoutSuffix) {
+    if (this.isValid() &&
+            ((isMoment(time) && time.isValid()) ||
+             createLocal(time).isValid())) {
+        return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
+    } else {
+        return this.localeData().invalidDate();
+    }
+}
+
+function toNow (withoutSuffix) {
+    return this.to(createLocal(), withoutSuffix);
+}
+
+// If passed a locale key, it will set the locale for this
+// instance.  Otherwise, it will return the locale configuration
+// variables for this instance.
+function locale (key) {
+    var newLocaleData;
+
+    if (key === undefined) {
+        return this._locale._abbr;
+    } else {
+        newLocaleData = getLocale(key);
+        if (newLocaleData != null) {
+            this._locale = newLocaleData;
+        }
+        return this;
+    }
+}
+
+var lang = deprecate(
+    'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+    function (key) {
+        if (key === undefined) {
+            return this.localeData();
+        } else {
+            return this.locale(key);
+        }
+    }
+);
+
+function localeData () {
+    return this._locale;
+}
+
+function startOf (units) {
+    units = normalizeUnits(units);
+    // the following switch intentionally omits break keywords
+    // to utilize falling through the cases.
+    switch (units) {
+        case 'year':
+            this.month(0);
+            /* falls through */
+        case 'quarter':
+        case 'month':
+            this.date(1);
+            /* falls through */
+        case 'week':
+        case 'isoWeek':
+        case 'day':
+        case 'date':
+            this.hours(0);
+            /* falls through */
+        case 'hour':
+            this.minutes(0);
+            /* falls through */
+        case 'minute':
+            this.seconds(0);
+            /* falls through */
+        case 'second':
+            this.milliseconds(0);
+    }
+
+    // weeks are a special case
+    if (units === 'week') {
+        this.weekday(0);
+    }
+    if (units === 'isoWeek') {
+        this.isoWeekday(1);
+    }
+
+    // quarters are also special
+    if (units === 'quarter') {
+        this.month(Math.floor(this.month() / 3) * 3);
+    }
+
+    return this;
+}
+
+function endOf (units) {
+    units = normalizeUnits(units);
+    if (units === undefined || units === 'millisecond') {
+        return this;
+    }
+
+    // 'date' is an alias for 'day', so it should be considered as such.
+    if (units === 'date') {
+        units = 'day';
+    }
+
+    return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
+}
+
+function valueOf () {
+    return this._d.valueOf() - ((this._offset || 0) * 60000);
+}
+
+function unix () {
+    return Math.floor(this.valueOf() / 1000);
+}
+
+function toDate () {
+    return new Date(this.valueOf());
+}
+
+function toArray () {
+    var m = this;
+    return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
+}
+
+function toObject () {
+    var m = this;
+    return {
+        years: m.year(),
+        months: m.month(),
+        date: m.date(),
+        hours: m.hours(),
+        minutes: m.minutes(),
+        seconds: m.seconds(),
+        milliseconds: m.milliseconds()
+    };
+}
+
+function toJSON () {
+    // new Date(NaN).toJSON() === null
+    return this.isValid() ? this.toISOString() : null;
+}
+
+function isValid$2 () {
+    return isValid(this);
+}
+
+function parsingFlags () {
+    return extend({}, getParsingFlags(this));
+}
+
+function invalidAt () {
+    return getParsingFlags(this).overflow;
+}
+
+function creationData() {
+    return {
+        input: this._i,
+        format: this._f,
+        locale: this._locale,
+        isUTC: this._isUTC,
+        strict: this._strict
+    };
+}
+
+// FORMATTING
+
+addFormatToken(0, ['gg', 2], 0, function () {
+    return this.weekYear() % 100;
+});
+
+addFormatToken(0, ['GG', 2], 0, function () {
+    return this.isoWeekYear() % 100;
+});
+
+function addWeekYearFormatToken (token, getter) {
+    addFormatToken(0, [token, token.length], 0, getter);
+}
+
+addWeekYearFormatToken('gggg',     'weekYear');
+addWeekYearFormatToken('ggggg',    'weekYear');
+addWeekYearFormatToken('GGGG',  'isoWeekYear');
+addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+// ALIASES
+
+addUnitAlias('weekYear', 'gg');
+addUnitAlias('isoWeekYear', 'GG');
+
+// PRIORITY
+
+addUnitPriority('weekYear', 1);
+addUnitPriority('isoWeekYear', 1);
+
+
+// PARSING
+
+addRegexToken('G',      matchSigned);
+addRegexToken('g',      matchSigned);
+addRegexToken('GG',     match1to2, match2);
+addRegexToken('gg',     match1to2, match2);
+addRegexToken('GGGG',   match1to4, match4);
+addRegexToken('gggg',   match1to4, match4);
+addRegexToken('GGGGG',  match1to6, match6);
+addRegexToken('ggggg',  match1to6, match6);
+
+addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
+    week[token.substr(0, 2)] = toInt(input);
+});
+
+addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+    week[token] = hooks.parseTwoDigitYear(input);
+});
+
+// MOMENTS
+
+function getSetWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy);
+}
+
+function getSetISOWeekYear (input) {
+    return getSetWeekYearHelper.call(this,
+            input, this.isoWeek(), this.isoWeekday(), 1, 4);
+}
+
+function getISOWeeksInYear () {
+    return weeksInYear(this.year(), 1, 4);
+}
+
+function getWeeksInYear () {
+    var weekInfo = this.localeData()._week;
+    return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+}
+
+function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+    var weeksTarget;
+    if (input == null) {
+        return weekOfYear(this, dow, doy).year;
+    } else {
+        weeksTarget = weeksInYear(input, dow, doy);
+        if (week > weeksTarget) {
+            week = weeksTarget;
+        }
+        return setWeekAll.call(this, input, week, weekday, dow, doy);
+    }
+}
+
+function setWeekAll(weekYear, week, weekday, dow, doy) {
+    var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+        date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+    this.year(date.getUTCFullYear());
+    this.month(date.getUTCMonth());
+    this.date(date.getUTCDate());
+    return this;
+}
+
+// FORMATTING
+
+addFormatToken('Q', 0, 'Qo', 'quarter');
+
+// ALIASES
+
+addUnitAlias('quarter', 'Q');
+
+// PRIORITY
+
+addUnitPriority('quarter', 7);
+
+// PARSING
+
+addRegexToken('Q', match1);
+addParseToken('Q', function (input, array) {
+    array[MONTH] = (toInt(input) - 1) * 3;
+});
+
+// MOMENTS
+
+function getSetQuarter (input) {
+    return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
+}
+
+// FORMATTING
+
+addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+// ALIASES
+
+addUnitAlias('date', 'D');
+
+// PRIOROITY
+addUnitPriority('date', 9);
+
+// PARSING
+
+addRegexToken('D',  match1to2);
+addRegexToken('DD', match1to2, match2);
+addRegexToken('Do', function (isStrict, locale) {
+    // TODO: Remove "ordinalParse" fallback in next major release.
+    return isStrict ?
+      (locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
+      locale._dayOfMonthOrdinalParseLenient;
+});
+
+addParseToken(['D', 'DD'], DATE);
+addParseToken('Do', function (input, array) {
+    array[DATE] = toInt(input.match(match1to2)[0]);
+});
+
+// MOMENTS
+
+var getSetDayOfMonth = makeGetSet('Date', true);
+
+// FORMATTING
+
+addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+// ALIASES
+
+addUnitAlias('dayOfYear', 'DDD');
+
+// PRIORITY
+addUnitPriority('dayOfYear', 4);
+
+// PARSING
+
+addRegexToken('DDD',  match1to3);
+addRegexToken('DDDD', match3);
+addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+    config._dayOfYear = toInt(input);
+});
+
+// HELPERS
+
+// MOMENTS
+
+function getSetDayOfYear (input) {
+    var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
+    return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
+}
+
+// FORMATTING
+
+addFormatToken('m', ['mm', 2], 0, 'minute');
+
+// ALIASES
+
+addUnitAlias('minute', 'm');
+
+// PRIORITY
+
+addUnitPriority('minute', 14);
+
+// PARSING
+
+addRegexToken('m',  match1to2);
+addRegexToken('mm', match1to2, match2);
+addParseToken(['m', 'mm'], MINUTE);
+
+// MOMENTS
+
+var getSetMinute = makeGetSet('Minutes', false);
+
+// FORMATTING
+
+addFormatToken('s', ['ss', 2], 0, 'second');
+
+// ALIASES
+
+addUnitAlias('second', 's');
+
+// PRIORITY
+
+addUnitPriority('second', 15);
+
+// PARSING
+
+addRegexToken('s',  match1to2);
+addRegexToken('ss', match1to2, match2);
+addParseToken(['s', 'ss'], SECOND);
+
+// MOMENTS
+
+var getSetSecond = makeGetSet('Seconds', false);
+
+// FORMATTING
+
+addFormatToken('S', 0, 0, function () {
+    return ~~(this.millisecond() / 100);
+});
+
+addFormatToken(0, ['SS', 2], 0, function () {
+    return ~~(this.millisecond() / 10);
+});
+
+addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+addFormatToken(0, ['SSSS', 4], 0, function () {
+    return this.millisecond() * 10;
+});
+addFormatToken(0, ['SSSSS', 5], 0, function () {
+    return this.millisecond() * 100;
+});
+addFormatToken(0, ['SSSSSS', 6], 0, function () {
+    return this.millisecond() * 1000;
+});
+addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+    return this.millisecond() * 10000;
+});
+addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+    return this.millisecond() * 100000;
+});
+addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+    return this.millisecond() * 1000000;
+});
+
+
+// ALIASES
+
+addUnitAlias('millisecond', 'ms');
+
+// PRIORITY
+
+addUnitPriority('millisecond', 16);
+
+// PARSING
+
+addRegexToken('S',    match1to3, match1);
+addRegexToken('SS',   match1to3, match2);
+addRegexToken('SSS',  match1to3, match3);
+
+var token;
+for (token = 'SSSS'; token.length <= 9; token += 'S') {
+    addRegexToken(token, matchUnsigned);
+}
+
+function parseMs(input, array) {
+    array[MILLISECOND] = toInt(('0.' + input) * 1000);
+}
+
+for (token = 'S'; token.length <= 9; token += 'S') {
+    addParseToken(token, parseMs);
+}
+// MOMENTS
+
+var getSetMillisecond = makeGetSet('Milliseconds', false);
+
+// FORMATTING
+
+addFormatToken('z',  0, 0, 'zoneAbbr');
+addFormatToken('zz', 0, 0, 'zoneName');
+
+// MOMENTS
+
+function getZoneAbbr () {
+    return this._isUTC ? 'UTC' : '';
+}
+
+function getZoneName () {
+    return this._isUTC ? 'Coordinated Universal Time' : '';
+}
+
+var proto = Moment.prototype;
+
+proto.add               = add;
+proto.calendar          = calendar$1;
+proto.clone             = clone;
+proto.diff              = diff;
+proto.endOf             = endOf;
+proto.format            = format;
+proto.from              = from;
+proto.fromNow           = fromNow;
+proto.to                = to;
+proto.toNow             = toNow;
+proto.get               = stringGet;
+proto.invalidAt         = invalidAt;
+proto.isAfter           = isAfter;
+proto.isBefore          = isBefore;
+proto.isBetween         = isBetween;
+proto.isSame            = isSame;
+proto.isSameOrAfter     = isSameOrAfter;
+proto.isSameOrBefore    = isSameOrBefore;
+proto.isValid           = isValid$2;
+proto.lang              = lang;
+proto.locale            = locale;
+proto.localeData        = localeData;
+proto.max               = prototypeMax;
+proto.min               = prototypeMin;
+proto.parsingFlags      = parsingFlags;
+proto.set               = stringSet;
+proto.startOf           = startOf;
+proto.subtract          = subtract;
+proto.toArray           = toArray;
+proto.toObject          = toObject;
+proto.toDate            = toDate;
+proto.toISOString       = toISOString;
+proto.inspect           = inspect;
+proto.toJSON            = toJSON;
+proto.toString          = toString;
+proto.unix              = unix;
+proto.valueOf           = valueOf;
+proto.creationData      = creationData;
+
+// Year
+proto.year       = getSetYear;
+proto.isLeapYear = getIsLeapYear;
+
+// Week Year
+proto.weekYear    = getSetWeekYear;
+proto.isoWeekYear = getSetISOWeekYear;
+
+// Quarter
+proto.quarter = proto.quarters = getSetQuarter;
+
+// Month
+proto.month       = getSetMonth;
+proto.daysInMonth = getDaysInMonth;
+
+// Week
+proto.week           = proto.weeks        = getSetWeek;
+proto.isoWeek        = proto.isoWeeks     = getSetISOWeek;
+proto.weeksInYear    = getWeeksInYear;
+proto.isoWeeksInYear = getISOWeeksInYear;
+
+// Day
+proto.date       = getSetDayOfMonth;
+proto.day        = proto.days             = getSetDayOfWeek;
+proto.weekday    = getSetLocaleDayOfWeek;
+proto.isoWeekday = getSetISODayOfWeek;
+proto.dayOfYear  = getSetDayOfYear;
+
+// Hour
+proto.hour = proto.hours = getSetHour;
+
+// Minute
+proto.minute = proto.minutes = getSetMinute;
+
+// Second
+proto.second = proto.seconds = getSetSecond;
+
+// Millisecond
+proto.millisecond = proto.milliseconds = getSetMillisecond;
+
+// Offset
+proto.utcOffset            = getSetOffset;
+proto.utc                  = setOffsetToUTC;
+proto.local                = setOffsetToLocal;
+proto.parseZone            = setOffsetToParsedOffset;
+proto.hasAlignedHourOffset = hasAlignedHourOffset;
+proto.isDST                = isDaylightSavingTime;
+proto.isLocal              = isLocal;
+proto.isUtcOffset          = isUtcOffset;
+proto.isUtc                = isUtc;
+proto.isUTC                = isUtc;
+
+// Timezone
+proto.zoneAbbr = getZoneAbbr;
+proto.zoneName = getZoneName;
+
+// Deprecations
+proto.dates  = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
+proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
+proto.years  = deprecate('years accessor is deprecated. Use year instead', getSetYear);
+proto.zone   = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
+proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
+
+function createUnix (input) {
+    return createLocal(input * 1000);
+}
+
+function createInZone () {
+    return createLocal.apply(null, arguments).parseZone();
+}
+
+function preParsePostFormat (string) {
+    return string;
+}
+
+var proto$1 = Locale.prototype;
+
+proto$1.calendar        = calendar;
+proto$1.longDateFormat  = longDateFormat;
+proto$1.invalidDate     = invalidDate;
+proto$1.ordinal         = ordinal;
+proto$1.preparse        = preParsePostFormat;
+proto$1.postformat      = preParsePostFormat;
+proto$1.relativeTime    = relativeTime;
+proto$1.pastFuture      = pastFuture;
+proto$1.set             = set;
+
+// Month
+proto$1.months            =        localeMonths;
+proto$1.monthsShort       =        localeMonthsShort;
+proto$1.monthsParse       =        localeMonthsParse;
+proto$1.monthsRegex       = monthsRegex;
+proto$1.monthsShortRegex  = monthsShortRegex;
+
+// Week
+proto$1.week = localeWeek;
+proto$1.firstDayOfYear = localeFirstDayOfYear;
+proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+// Day of Week
+proto$1.weekdays       =        localeWeekdays;
+proto$1.weekdaysMin    =        localeWeekdaysMin;
+proto$1.weekdaysShort  =        localeWeekdaysShort;
+proto$1.weekdaysParse  =        localeWeekdaysParse;
+
+proto$1.weekdaysRegex       =        weekdaysRegex;
+proto$1.weekdaysShortRegex  =        weekdaysShortRegex;
+proto$1.weekdaysMinRegex    =        weekdaysMinRegex;
+
+// Hours
+proto$1.isPM = localeIsPM;
+proto$1.meridiem = localeMeridiem;
+
+function get$1 (format, index, field, setter) {
+    var locale = getLocale();
+    var utc = createUTC().set(setter, index);
+    return locale[field](utc, format);
+}
+
+function listMonthsImpl (format, index, field) {
+    if (isNumber(format)) {
+        index = format;
+        format = undefined;
+    }
+
+    format = format || '';
+
+    if (index != null) {
+        return get$1(format, index, field, 'month');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 12; i++) {
+        out[i] = get$1(format, i, field, 'month');
+    }
+    return out;
+}
+
+// ()
+// (5)
+// (fmt, 5)
+// (fmt)
+// (true)
+// (true, 5)
+// (true, fmt, 5)
+// (true, fmt)
+function listWeekdaysImpl (localeSorted, format, index, field) {
+    if (typeof localeSorted === 'boolean') {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    } else {
+        format = localeSorted;
+        index = format;
+        localeSorted = false;
+
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+    }
+
+    var locale = getLocale(),
+        shift = localeSorted ? locale._week.dow : 0;
+
+    if (index != null) {
+        return get$1(format, (index + shift) % 7, field, 'day');
+    }
+
+    var i;
+    var out = [];
+    for (i = 0; i < 7; i++) {
+        out[i] = get$1(format, (i + shift) % 7, field, 'day');
+    }
+    return out;
+}
+
+function listMonths (format, index) {
+    return listMonthsImpl(format, index, 'months');
+}
+
+function listMonthsShort (format, index) {
+    return listMonthsImpl(format, index, 'monthsShort');
+}
+
+function listWeekdays (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+}
+
+function listWeekdaysShort (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+}
+
+function listWeekdaysMin (localeSorted, format, index) {
+    return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+}
+
+getSetGlobalLocale('en', {
+    dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+    ordinal : function (number) {
+        var b = number % 10,
+            output = (toInt(number % 100 / 10) === 1) ? 'th' :
+            (b === 1) ? 'st' :
+            (b === 2) ? 'nd' :
+            (b === 3) ? 'rd' : 'th';
+        return number + output;
+    }
+});
+
+// Side effect imports
+hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
+hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
+
+var mathAbs = Math.abs;
+
+function abs () {
+    var data           = this._data;
+
+    this._milliseconds = mathAbs(this._milliseconds);
+    this._days         = mathAbs(this._days);
+    this._months       = mathAbs(this._months);
+
+    data.milliseconds  = mathAbs(data.milliseconds);
+    data.seconds       = mathAbs(data.seconds);
+    data.minutes       = mathAbs(data.minutes);
+    data.hours         = mathAbs(data.hours);
+    data.months        = mathAbs(data.months);
+    data.years         = mathAbs(data.years);
+
+    return this;
+}
+
+function addSubtract$1 (duration, input, value, direction) {
+    var other = createDuration(input, value);
+
+    duration._milliseconds += direction * other._milliseconds;
+    duration._days         += direction * other._days;
+    duration._months       += direction * other._months;
+
+    return duration._bubble();
+}
+
+// supports only 2.0-style add(1, 's') or add(duration)
+function add$1 (input, value) {
+    return addSubtract$1(this, input, value, 1);
+}
+
+// supports only 2.0-style subtract(1, 's') or subtract(duration)
+function subtract$1 (input, value) {
+    return addSubtract$1(this, input, value, -1);
+}
+
+function absCeil (number) {
+    if (number < 0) {
+        return Math.floor(number);
+    } else {
+        return Math.ceil(number);
+    }
+}
+
+function bubble () {
+    var milliseconds = this._milliseconds;
+    var days         = this._days;
+    var months       = this._months;
+    var data         = this._data;
+    var seconds, minutes, hours, years, monthsFromDays;
+
+    // if we have a mix of positive and negative values, bubble down first
+    // check: https://github.com/moment/moment/issues/2166
+    if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
+            (milliseconds <= 0 && days <= 0 && months <= 0))) {
+        milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+        days = 0;
+        months = 0;
+    }
+
+    // The following code bubbles up values, see the tests for
+    // examples of what that means.
+    data.milliseconds = milliseconds % 1000;
+
+    seconds           = absFloor(milliseconds / 1000);
+    data.seconds      = seconds % 60;
+
+    minutes           = absFloor(seconds / 60);
+    data.minutes      = minutes % 60;
+
+    hours             = absFloor(minutes / 60);
+    data.hours        = hours % 24;
+
+    days += absFloor(hours / 24);
+
+    // convert days to months
+    monthsFromDays = absFloor(daysToMonths(days));
+    months += monthsFromDays;
+    days -= absCeil(monthsToDays(monthsFromDays));
+
+    // 12 months -> 1 year
+    years = absFloor(months / 12);
+    months %= 12;
+
+    data.days   = days;
+    data.months = months;
+    data.years  = years;
+
+    return this;
+}
+
+function daysToMonths (days) {
+    // 400 years have 146097 days (taking into account leap year rules)
+    // 400 years have 12 months === 4800
+    return days * 4800 / 146097;
+}
+
+function monthsToDays (months) {
+    // the reverse of daysToMonths
+    return months * 146097 / 4800;
+}
+
+function as (units) {
+    if (!this.isValid()) {
+        return NaN;
+    }
+    var days;
+    var months;
+    var milliseconds = this._milliseconds;
+
+    units = normalizeUnits(units);
+
+    if (units === 'month' || units === 'year') {
+        days   = this._days   + milliseconds / 864e5;
+        months = this._months + daysToMonths(days);
+        return units === 'month' ? months : months / 12;
+    } else {
+        // handle milliseconds separately because of floating point math errors (issue #1867)
+        days = this._days + Math.round(monthsToDays(this._months));
+        switch (units) {
+            case 'week'   : return days / 7     + milliseconds / 6048e5;
+            case 'day'    : return days         + milliseconds / 864e5;
+            case 'hour'   : return days * 24    + milliseconds / 36e5;
+            case 'minute' : return days * 1440  + milliseconds / 6e4;
+            case 'second' : return days * 86400 + milliseconds / 1000;
+            // Math.floor prevents floating point math errors here
+            case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
+            default: throw new Error('Unknown unit ' + units);
+        }
+    }
+}
+
+// TODO: Use this.as('ms')?
+function valueOf$1 () {
+    if (!this.isValid()) {
+        return NaN;
+    }
+    return (
+        this._milliseconds +
+        this._days * 864e5 +
+        (this._months % 12) * 2592e6 +
+        toInt(this._months / 12) * 31536e6
+    );
+}
+
+function makeAs (alias) {
+    return function () {
+        return this.as(alias);
+    };
+}
+
+var asMilliseconds = makeAs('ms');
+var asSeconds      = makeAs('s');
+var asMinutes      = makeAs('m');
+var asHours        = makeAs('h');
+var asDays         = makeAs('d');
+var asWeeks        = makeAs('w');
+var asMonths       = makeAs('M');
+var asYears        = makeAs('y');
+
+function clone$1 () {
+    return createDuration(this);
+}
+
+function get$2 (units) {
+    units = normalizeUnits(units);
+    return this.isValid() ? this[units + 's']() : NaN;
+}
+
+function makeGetter(name) {
+    return function () {
+        return this.isValid() ? this._data[name] : NaN;
+    };
+}
+
+var milliseconds = makeGetter('milliseconds');
+var seconds      = makeGetter('seconds');
+var minutes      = makeGetter('minutes');
+var hours        = makeGetter('hours');
+var days         = makeGetter('days');
+var months       = makeGetter('months');
+var years        = makeGetter('years');
+
+function weeks () {
+    return absFloor(this.days() / 7);
+}
+
+var round = Math.round;
+var thresholds = {
+    ss: 44,         // a few seconds to seconds
+    s : 45,         // seconds to minute
+    m : 45,         // minutes to hour
+    h : 22,         // hours to day
+    d : 26,         // days to month
+    M : 11          // months to year
+};
+
+// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+    return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+}
+
+function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
+    var duration = createDuration(posNegDuration).abs();
+    var seconds  = round(duration.as('s'));
+    var minutes  = round(duration.as('m'));
+    var hours    = round(duration.as('h'));
+    var days     = round(duration.as('d'));
+    var months   = round(duration.as('M'));
+    var years    = round(duration.as('y'));
+
+    var a = seconds <= thresholds.ss && ['s', seconds]  ||
+            seconds < thresholds.s   && ['ss', seconds] ||
+            minutes <= 1             && ['m']           ||
+            minutes < thresholds.m   && ['mm', minutes] ||
+            hours   <= 1             && ['h']           ||
+            hours   < thresholds.h   && ['hh', hours]   ||
+            days    <= 1             && ['d']           ||
+            days    < thresholds.d   && ['dd', days]    ||
+            months  <= 1             && ['M']           ||
+            months  < thresholds.M   && ['MM', months]  ||
+            years   <= 1             && ['y']           || ['yy', years];
+
+    a[2] = withoutSuffix;
+    a[3] = +posNegDuration > 0;
+    a[4] = locale;
+    return substituteTimeAgo.apply(null, a);
+}
+
+// This function allows you to set the rounding function for relative time strings
+function getSetRelativeTimeRounding (roundingFunction) {
+    if (roundingFunction === undefined) {
+        return round;
+    }
+    if (typeof(roundingFunction) === 'function') {
+        round = roundingFunction;
+        return true;
+    }
+    return false;
+}
+
+// This function allows you to set a threshold for relative time strings
+function getSetRelativeTimeThreshold (threshold, limit) {
+    if (thresholds[threshold] === undefined) {
+        return false;
+    }
+    if (limit === undefined) {
+        return thresholds[threshold];
+    }
+    thresholds[threshold] = limit;
+    if (threshold === 's') {
+        thresholds.ss = limit - 1;
+    }
+    return true;
+}
+
+function humanize (withSuffix) {
+    if (!this.isValid()) {
+        return this.localeData().invalidDate();
+    }
+
+    var locale = this.localeData();
+    var output = relativeTime$1(this, !withSuffix, locale);
+
+    if (withSuffix) {
+        output = locale.pastFuture(+this, output);
+    }
+
+    return locale.postformat(output);
+}
+
+var abs$1 = Math.abs;
+
+function sign(x) {
+    return ((x > 0) - (x < 0)) || +x;
+}
+
+function toISOString$1() {
+    // for ISO strings we do not use the normal bubbling rules:
+    //  * milliseconds bubble up until they become hours
+    //  * days do not bubble at all
+    //  * months bubble up until they become years
+    // This is because there is no context-free conversion between hours and days
+    // (think of clock changes)
+    // and also not between days and months (28-31 days per month)
+    if (!this.isValid()) {
+        return this.localeData().invalidDate();
+    }
+
+    var seconds = abs$1(this._milliseconds) / 1000;
+    var days         = abs$1(this._days);
+    var months       = abs$1(this._months);
+    var minutes, hours, years;
+
+    // 3600 seconds -> 60 minutes -> 1 hour
+    minutes           = absFloor(seconds / 60);
+    hours             = absFloor(minutes / 60);
+    seconds %= 60;
+    minutes %= 60;
+
+    // 12 months -> 1 year
+    years  = absFloor(months / 12);
+    months %= 12;
+
+
+    // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+    var Y = years;
+    var M = months;
+    var D = days;
+    var h = hours;
+    var m = minutes;
+    var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
+    var total = this.asSeconds();
+
+    if (!total) {
+        // this is the same as C#'s (Noda) and python (isodate)...
+        // but not other JS (goog.date)
+        return 'P0D';
+    }
+
+    var totalSign = total < 0 ? '-' : '';
+    var ymSign = sign(this._months) !== sign(total) ? '-' : '';
+    var daysSign = sign(this._days) !== sign(total) ? '-' : '';
+    var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
+
+    return totalSign + 'P' +
+        (Y ? ymSign + Y + 'Y' : '') +
+        (M ? ymSign + M + 'M' : '') +
+        (D ? daysSign + D + 'D' : '') +
+        ((h || m || s) ? 'T' : '') +
+        (h ? hmsSign + h + 'H' : '') +
+        (m ? hmsSign + m + 'M' : '') +
+        (s ? hmsSign + s + 'S' : '');
+}
+
+var proto$2 = Duration.prototype;
+
+proto$2.isValid        = isValid$1;
+proto$2.abs            = abs;
+proto$2.add            = add$1;
+proto$2.subtract       = subtract$1;
+proto$2.as             = as;
+proto$2.asMilliseconds = asMilliseconds;
+proto$2.asSeconds      = asSeconds;
+proto$2.asMinutes      = asMinutes;
+proto$2.asHours        = asHours;
+proto$2.asDays         = asDays;
+proto$2.asWeeks        = asWeeks;
+proto$2.asMonths       = asMonths;
+proto$2.asYears        = asYears;
+proto$2.valueOf        = valueOf$1;
+proto$2._bubble        = bubble;
+proto$2.clone          = clone$1;
+proto$2.get            = get$2;
+proto$2.milliseconds   = milliseconds;
+proto$2.seconds        = seconds;
+proto$2.minutes        = minutes;
+proto$2.hours          = hours;
+proto$2.days           = days;
+proto$2.weeks          = weeks;
+proto$2.months         = months;
+proto$2.years          = years;
+proto$2.humanize       = humanize;
+proto$2.toISOString    = toISOString$1;
+proto$2.toString       = toISOString$1;
+proto$2.toJSON         = toISOString$1;
+proto$2.locale         = locale;
+proto$2.localeData     = localeData;
+
+// Deprecations
+proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
+proto$2.lang = lang;
+
+// Side effect imports
+
+// FORMATTING
+
+addFormatToken('X', 0, 0, 'unix');
+addFormatToken('x', 0, 0, 'valueOf');
+
+// PARSING
+
+addRegexToken('x', matchSigned);
+addRegexToken('X', matchTimestamp);
+addParseToken('X', function (input, array, config) {
+    config._d = new Date(parseFloat(input, 10) * 1000);
+});
+addParseToken('x', function (input, array, config) {
+    config._d = new Date(toInt(input));
+});
+
+// Side effect imports
+
+
+hooks.version = '2.20.1';
+
+setHookCallback(createLocal);
+
+hooks.fn                    = proto;
+hooks.min                   = min;
+hooks.max                   = max;
+hooks.now                   = now;
+hooks.utc                   = createUTC;
+hooks.unix                  = createUnix;
+hooks.months                = listMonths;
+hooks.isDate                = isDate;
+hooks.locale                = getSetGlobalLocale;
+hooks.invalid               = createInvalid;
+hooks.duration              = createDuration;
+hooks.isMoment              = isMoment;
+hooks.weekdays              = listWeekdays;
+hooks.parseZone             = createInZone;
+hooks.localeData            = getLocale;
+hooks.isDuration            = isDuration;
+hooks.monthsShort           = listMonthsShort;
+hooks.weekdaysMin           = listWeekdaysMin;
+hooks.defineLocale          = defineLocale;
+hooks.updateLocale          = updateLocale;
+hooks.locales               = listLocales;
+hooks.weekdaysShort         = listWeekdaysShort;
+hooks.normalizeUnits        = normalizeUnits;
+hooks.relativeTimeRounding  = getSetRelativeTimeRounding;
+hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+hooks.calendarFormat        = getCalendarFormat;
+hooks.prototype             = proto;
+
+// currently HTML5 input type only supports 24-hour formats
+hooks.HTML5_FMT = {
+    DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm',             // <input type="datetime-local" />
+    DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss',  // <input type="datetime-local" step="1" />
+    DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS',   // <input type="datetime-local" step="0.001" />
+    DATE: 'YYYY-MM-DD',                             // <input type="date" />
+    TIME: 'HH:mm',                                  // <input type="time" />
+    TIME_SECONDS: 'HH:mm:ss',                       // <input type="time" step="1" />
+    TIME_MS: 'HH:mm:ss.SSS',                        // <input type="time" step="0.001" />
+    WEEK: 'YYYY-[W]WW',                             // <input type="week" />
+    MONTH: 'YYYY-MM'                                // <input type="month" />
+};
+
+return hooks;
+
+})));

+ 19 - 0
js_sdk/js-base64/.attic/test-moment/yoshinoya.js

@@ -0,0 +1,19 @@
+/*
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+describe('Yoshinoya', function () {
+    it('.encode', is(Base64.encode('𠮷野家'), '8KCut+mHjuWutg=='));
+    it('.encodeURI', is(Base64.encodeURI('𠮷野家'), '8KCut-mHjuWutg'));
+    it('.decode', is(Base64.decode('8KCut+mHjuWutg=='), '𠮷野家'));
+    it('.decode', is(Base64.decode('8KCut-mHjuWutg'), '𠮷野家'));
+    /* it('.decode', is(Base64.decode('7aGC7b636YeO5a62'), '𠮷野家')); */
+});

BIN
js_sdk/js-base64/1x1.png


+ 27 - 0
js_sdk/js-base64/LICENSE.md

@@ -0,0 +1,27 @@
+Copyright (c) 2014, Dan Kogai
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of {{{project}}} nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 94 - 0
js_sdk/js-base64/README.md

@@ -0,0 +1,94 @@
+[![build status](https://secure.travis-ci.org/dankogai/js-base64.png)](http://travis-ci.org/dankogai/js-base64)
+
+# base64.js
+
+Yet another Base64 transcoder
+
+## Install
+
+```javascript
+$ npm install --save js-base64
+```
+
+If you are using it on ES6 transpilers, you may also need:
+
+```javascript
+$ npm install --save babel-preset-env
+```
+
+Note `js-base64` itself is stand-alone so its `package.json` has no `dependencies`.  However, it is also tested on ES6 environment so `"babel-preset-env": "^1.7.0"` is on `devDependencies`.
+
+
+## Usage
+
+### In Browser
+
+```html
+<script src="base64.js"></script>
+```
+
+### node.js
+
+```javascript
+var Base64 = require('js-base64').Base64;
+```
+
+## es6+
+
+```javascript
+import { Base64 } from 'js-base64';
+```
+
+## SYNOPSIS
+
+```javascript
+Base64.encode('dankogai');  // ZGFua29nYWk=
+Base64.encode('小飼弾');    // 5bCP6aO85by+
+Base64.encodeURI('小飼弾'); // 5bCP6aO85by-
+
+Base64.decode('ZGFua29nYWk=');  // dankogai
+Base64.decode('5bCP6aO85by+');  // 小飼弾
+// note .decodeURI() is unnecessary since it accepts both flavors
+Base64.decode('5bCP6aO85by-');  // 小飼弾
+```
+
+### String Extension for ES5
+
+```javascript
+if (Base64.extendString) {
+    // you have to explicitly extend String.prototype
+    Base64.extendString();
+    // once extended, you can do the following
+    'dankogai'.toBase64();       // ZGFua29nYWk=
+    '小飼弾'.toBase64();         // 5bCP6aO85by+
+    '小飼弾'.toBase64(true);     // 5bCP6aO85by-
+    '小飼弾'.toBase64URI();      // 5bCP6aO85by-
+    'ZGFua29nYWk='.fromBase64(); // dankogai
+    '5bCP6aO85by+'.fromBase64(); // 小飼弾
+    '5bCP6aO85by-'.fromBase64(); // 小飼弾
+}
+```
+
+### TypeScript
+
+TypeScript 2.0 type definition was added to the [DefinitelyTyped repository](https://github.com/DefinitelyTyped/DefinitelyTyped).
+
+```bash
+$ npm install --save @types/js-base64
+```
+
+## `.decode()` vs `.atob` (and `.encode()` vs `btoa()`)
+
+Suppose you have:
+
+```
+var pngBase64 = 
+  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
+```
+
+Which is a Base64-encoded 1x1 transparent PNG, **DO NOT USE** `Base64.decode(pngBase64)`.  Use `Base64.atob(pngBase64)` instead.  `Base64.decode()` decodes to UTF-8 string while `Base64.atob()` decodes to bytes, which is compatible to browser built-in `atob()` (Which is absent in node.js).  The same rule applies to the opposite direction.
+
+
+## SEE ALSO
+
++ http://en.wikipedia.org/wiki/Base64

+ 47 - 0
js_sdk/js-base64/base64.html

@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!-- $Id: base64.html,v 1.1 2009/03/01 22:00:28 dankogai Exp dankogai $ -->
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Demo for base64.js</title>
+</head>
+<body>
+<h1>Demo for base64.js</h1>
+<p>$Id: base64.html,v 1.1 2009/03/01 22:00:28 dankogai Exp dankogai $</p>
+<table width="640"><tbody>
+<tr><th width="50%">Text</th><th>Base64
+(URL Safe <input id="encodeURI" type="checkbox" onclick="doit()">)</th></tr>
+<tr>
+<th><textarea id="srctxt" cols="32" rows="4" onkeyup="doit()">
+</textarea></th>
+<th><textarea id="base64" cols="32" rows="4" onkeyup="
+    $('srctxt').value = Base64.decode(this.value);
+    doit();
+    if (1 /*@cc_on -1 @*/) $('data').src = 'data:text/plain;base64,' + this.value;
+"></textarea></th>
+</tr>
+<tr><th width="50%">Roundtrip</th><th>iframe w/ data: (no IE)</th></tr>
+<tr>
+<th><textarea id="roundtrip" cols=32" rows="4" disabled></textarea></th>
+<th><iframe id="data" width="80%" height="64"></iframe></th>
+</tr>
+</tbody></table>
+
+
+<script src="./base64.js"></script>
+<script>
+$ = function(id){ return document.getElementById(id) };
+function doit(){
+    var encoded = Base64[ 
+        'encode' + ($('encodeURI').checked ? 'URI' : '')
+     ]($('srctxt').value);
+    $('base64').value = encoded;
+    if (1 /*@cc_on -1 @*/) {
+        $('data').src = 'data:text/plain;base64,' 
+            + Base64.encode(Base64.decode(encoded));
+    }
+    $('roundtrip').value = Base64.decode(encoded);
+}
+</script>
+</body>
+</html>

+ 231 - 0
js_sdk/js-base64/base64.js

@@ -0,0 +1,231 @@
+/*
+ *  base64.js
+ *
+ *  Licensed under the BSD 3-Clause License.
+ *    http://opensource.org/licenses/BSD-3-Clause
+ *
+ *  References:
+ *    http://en.wikipedia.org/wiki/Base64
+ */
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined'
+        ? module.exports = factory(global)
+        : typeof define === 'function' && define.amd
+        ? define(factory) : factory(global)
+}((
+    typeof self !== 'undefined' ? self
+        : typeof window !== 'undefined' ? window
+        : typeof global !== 'undefined' ? global
+: this
+), function(global) {
+    'use strict';
+    // existing version for noConflict()
+    var _Base64 = global.Base64;
+    var version = "2.4.9";
+    // if node.js and NOT React Native, we use Buffer
+    var buffer;
+    if (typeof module !== 'undefined' && module.exports) {
+        try {
+            buffer = eval("require('buffer').Buffer");
+        } catch (err) {
+            buffer = undefined;
+        }
+    }
+    // constants
+    var b64chars
+        = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+    var b64tab = function(bin) {
+        var t = {};
+        for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
+        return t;
+    }(b64chars);
+    var fromCharCode = String.fromCharCode;
+    // encoder stuff
+    var cb_utob = function(c) {
+        if (c.length < 2) {
+            var cc = c.charCodeAt(0);
+            return cc < 0x80 ? c
+                : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+                                + fromCharCode(0x80 | (cc & 0x3f)))
+                : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+                   + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
+                   + fromCharCode(0x80 | ( cc         & 0x3f)));
+        } else {
+            var cc = 0x10000
+                + (c.charCodeAt(0) - 0xD800) * 0x400
+                + (c.charCodeAt(1) - 0xDC00);
+            return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
+                    + fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
+                    + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
+                    + fromCharCode(0x80 | ( cc         & 0x3f)));
+        }
+    };
+    var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
+    var utob = function(u) {
+        return u.replace(re_utob, cb_utob);
+    };
+    var cb_encode = function(ccc) {
+        var padlen = [0, 2, 1][ccc.length % 3],
+        ord = ccc.charCodeAt(0) << 16
+            | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
+            | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
+        chars = [
+            b64chars.charAt( ord >>> 18),
+            b64chars.charAt((ord >>> 12) & 63),
+            padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
+            padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
+        ];
+        return chars.join('');
+    };
+    var btoa = global.btoa ? function(b) {
+        return global.btoa(b);
+    } : function(b) {
+        return b.replace(/[\s\S]{1,3}/g, cb_encode);
+    };
+    var _encode = buffer ?
+        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
+        ? function (u) {
+            return (u.constructor === buffer.constructor ? u : buffer.from(u))
+                .toString('base64')
+        }
+        :  function (u) {
+            return (u.constructor === buffer.constructor ? u : new  buffer(u))
+                .toString('base64')
+        }
+        : function (u) { return btoa(utob(u)) }
+    ;
+    var encode = function(u, urisafe) {
+        return !urisafe
+            ? _encode(String(u))
+            : _encode(String(u)).replace(/[+\/]/g, function(m0) {
+                return m0 == '+' ? '-' : '_';
+            }).replace(/=/g, '');
+    };
+    var encodeURI = function(u) { return encode(u, true) };
+    // decoder stuff
+    var re_btou = new RegExp([
+        '[\xC0-\xDF][\x80-\xBF]',
+        '[\xE0-\xEF][\x80-\xBF]{2}',
+        '[\xF0-\xF7][\x80-\xBF]{3}'
+    ].join('|'), 'g');
+    var cb_btou = function(cccc) {
+        switch(cccc.length) {
+        case 4:
+            var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
+                |    ((0x3f & cccc.charCodeAt(1)) << 12)
+                |    ((0x3f & cccc.charCodeAt(2)) <<  6)
+                |     (0x3f & cccc.charCodeAt(3)),
+            offset = cp - 0x10000;
+            return (fromCharCode((offset  >>> 10) + 0xD800)
+                    + fromCharCode((offset & 0x3FF) + 0xDC00));
+        case 3:
+            return fromCharCode(
+                ((0x0f & cccc.charCodeAt(0)) << 12)
+                    | ((0x3f & cccc.charCodeAt(1)) << 6)
+                    |  (0x3f & cccc.charCodeAt(2))
+            );
+        default:
+            return  fromCharCode(
+                ((0x1f & cccc.charCodeAt(0)) << 6)
+                    |  (0x3f & cccc.charCodeAt(1))
+            );
+        }
+    };
+    var btou = function(b) {
+        return b.replace(re_btou, cb_btou);
+    };
+    var cb_decode = function(cccc) {
+        var len = cccc.length,
+        padlen = len % 4,
+        n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
+            | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
+            | (len > 2 ? b64tab[cccc.charAt(2)] <<  6 : 0)
+            | (len > 3 ? b64tab[cccc.charAt(3)]       : 0),
+        chars = [
+            fromCharCode( n >>> 16),
+            fromCharCode((n >>>  8) & 0xff),
+            fromCharCode( n         & 0xff)
+        ];
+        chars.length -= [0, 0, 2, 1][padlen];
+        return chars.join('');
+    };
+    var atob = global.atob ? function(a) {
+        return global.atob(a);
+    } : function(a){
+        return a.replace(/[\s\S]{1,4}/g, cb_decode);
+    };
+    var _decode = buffer ?
+        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
+        ? function(a) {
+            return (a.constructor === buffer.constructor
+                    ? a : buffer.from(a, 'base64')).toString();
+        }
+        : function(a) {
+            return (a.constructor === buffer.constructor
+                    ? a : new buffer(a, 'base64')).toString();
+        }
+        : function(a) { return btou(atob(a)) };
+    var decode = function(a){
+        return _decode(
+            String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
+                .replace(/[^A-Za-z0-9\+\/]/g, '')
+        );
+    };
+    var noConflict = function() {
+        var Base64 = global.Base64;
+        global.Base64 = _Base64;
+        return Base64;
+    };
+    // export Base64
+    global.Base64 = {
+        VERSION: version,
+        atob: atob,
+        btoa: btoa,
+        fromBase64: decode,
+        toBase64: encode,
+        utob: utob,
+        encode: encode,
+        encodeURI: encodeURI,
+        btou: btou,
+        decode: decode,
+        noConflict: noConflict,
+        __buffer__: buffer
+    };
+    // if ES5 is available, make Base64.extendString() available
+    if (typeof Object.defineProperty === 'function') {
+        var noEnum = function(v){
+            return {value:v,enumerable:false,writable:true,configurable:true};
+        };
+        global.Base64.extendString = function () {
+            Object.defineProperty(
+                String.prototype, 'fromBase64', noEnum(function () {
+                    return decode(this)
+                }));
+            Object.defineProperty(
+                String.prototype, 'toBase64', noEnum(function (urisafe) {
+                    return encode(this, urisafe)
+                }));
+            Object.defineProperty(
+                String.prototype, 'toBase64URI', noEnum(function () {
+                    return encode(this, true)
+                }));
+        };
+    }
+    //
+    // export Base64 to the namespace
+    //
+    if (global['Meteor']) { // Meteor.js
+        Base64 = global.Base64;
+    }
+    // module.exports and AMD are mutually exclusive.
+    // module.exports has precedence.
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports.Base64 = global.Base64;
+    }
+    else if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define([], function(){ return global.Base64 });
+    }
+    // that's it!
+    return {Base64: global.Base64}
+}));

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
js_sdk/js-base64/base64.min.js


+ 18 - 0
js_sdk/js-base64/bower.json

@@ -0,0 +1,18 @@
+{
+  "name": "js-base64",
+  "version": "2.4.9",
+  "license": "BSD-3-Clause",
+  "main": [
+    "./base64.js"
+  ],
+  "ignore": [
+    "old",
+    "test",
+    ".gitignore",
+    ".travis.yml",
+    "base64.html",
+    "package.json"
+  ],
+  "dependencies": {
+  }
+}

+ 9 - 0
js_sdk/js-base64/package.js

@@ -0,0 +1,9 @@
+Package.describe({
+    summary: "Yet another Base64 transcoder"
+})
+
+Package.on_use(function(api){
+    api.export('Base64');
+
+    api.add_files(['base64.js'], 'server');
+});

+ 59 - 0
js_sdk/js-base64/package.json

@@ -0,0 +1,59 @@
+{
+  "_from": "js-base64",
+  "_id": "js-base64@2.4.9",
+  "_inBundle": false,
+  "_integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==",
+  "_location": "/js-base64",
+  "_phantomChildren": {},
+  "_requested": {
+    "type": "tag",
+    "registry": true,
+    "raw": "js-base64",
+    "name": "js-base64",
+    "escapedName": "js-base64",
+    "rawSpec": "",
+    "saveSpec": null,
+    "fetchSpec": "latest"
+  },
+  "_requiredBy": [
+    "#DEV:/",
+    "#USER"
+  ],
+  "_resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz",
+  "_shasum": "748911fb04f48a60c4771b375cac45a80df11c03",
+  "_spec": "js-base64",
+  "_where": "C:\\Users\\dcloud\\Documents\\HBuilderProjects\\test-npm",
+  "author": {
+    "name": "Dan Kogai"
+  },
+  "bugs": {
+    "url": "https://github.com/dankogai/js-base64/issues"
+  },
+  "bundleDependencies": false,
+  "deprecated": false,
+  "description": "Yet another Base64 transcoder in pure-JS",
+  "devDependencies": {
+    "babel-preset-env": "^1.7.0",
+    "babel-register": "^6.26.0",
+    "mocha": "*"
+  },
+  "directories": {
+    "test": "test"
+  },
+  "gitHead": "8bfa436f733bec60c95c720e1d720c28b43ae0b2",
+  "homepage": "https://github.com/dankogai/js-base64#readme",
+  "keywords": [
+    "base64"
+  ],
+  "license": "BSD-3-Clause",
+  "main": "base64.js",
+  "name": "js-base64",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/dankogai/js-base64.git"
+  },
+  "scripts": {
+    "test": "mocha --compilers js:babel-register"
+  },
+  "version": "2.4.9"
+}

+ 44 - 0
js_sdk/js-base64/test/dankogai.js

@@ -0,0 +1,44 @@
+/*
+ * $Id: dankogai.js,v 0.4 2012/08/24 05:23:18 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+describe('basic', function () {
+    it('d',    is(Base64.encode('d'),    'ZA=='));
+    it('da',   is(Base64.encode('da'),   'ZGE='));
+    it('dan',  is(Base64.encode('dan'),  'ZGFu'));
+    it('ZA==', is(Base64.decode('ZA=='), 'd'   ));
+    it('ZGE=', is(Base64.decode('ZGE='), 'da'  ));
+    it('ZGFu', is(Base64.decode('ZGFu'), 'dan' ));
+});
+
+describe('whitespace', function () {
+    it('Z A==', is(Base64.decode('ZA =='), 'd'   ));
+    it('ZG E=', is(Base64.decode('ZG E='), 'da'  ));
+    it('ZGF u', is(Base64.decode('ZGF u'), 'dan' ));
+});
+
+describe('null', function () {
+    it('\\0',       is(Base64.encode('\0'),     'AA=='));
+    it('\\0\\0',    is(Base64.encode('\0\0'),   'AAA='));
+    it('\\0\\0\\0', is(Base64.encode('\0\0\0'), 'AAAA'));
+    it('AA==',      is(Base64.decode('AA=='), '\0'    ));
+    it('AAA=',      is(Base64.decode('AAA='), '\0\0'  ));
+    it('AAAA',      is(Base64.decode('AAAA'), '\0\0\0'));
+});
+
+describe('Base64', function () {
+    it('.encode', is(Base64.encode('小飼弾'), '5bCP6aO85by+'));
+    it('.encodeURI', is(Base64.encodeURI('小飼弾'), '5bCP6aO85by-'));
+    it('.decode', is(Base64.decode('5bCP6aO85by+'), '小飼弾'));
+    it('.decode', is(Base64.decode('5bCP6aO85by-'), '小飼弾'));
+});

+ 24 - 0
js_sdk/js-base64/test/es5.js

@@ -0,0 +1,24 @@
+/*
+ * $Id: es5.js,v 0.1 2012/08/23 19:43:17 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+if ('extendString' in Base64){
+    Base64.extendString();
+    describe('String', function () {
+        it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
+        it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
+        it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
+        it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
+        it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
+    });
+}

+ 25 - 0
js_sdk/js-base64/test/es6.js

@@ -0,0 +1,25 @@
+/*
+ * $Id: es6.js,v 0.1 2017/11/29 21:43:17 ufolux Exp ufolux $
+ *
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+import {Base64} from '../base64'
+
+var assert = assert || require("assert");
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+if ('extendString' in Base64){
+    Base64.extendString();
+    describe('String', function () {
+        it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
+        it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
+        it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
+        it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
+        it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
+    });
+}

+ 39 - 0
js_sdk/js-base64/test/index.html

@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
+  </head>
+  <body>
+    <div id="mocha"></div>
+
+    <script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
+    <script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>
+    <script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
+    <script>
+      mocha.setup('bdd');
+    </script>
+    <script src="../base64.js"></script>
+    <script>
+      var assert = function(expr, msg) {
+        if (!expr) throw new Error(msg || 'failed');
+      };
+      assert.equal = function(a, b, msg) {
+        if (a !== b) throw new Error(msg || ('failed : '+a+','+b));
+      };
+    </script>
+    <script src="./dankogai.js"></script>
+    <script src="./es5.js"></script>
+    <script src="./large.js"></script>
+    <script src="./yoshinoya.js"></script>
+    <script>
+      $(function() {
+        mocha.run();
+      });
+    </script>
+</head>
+<body>
+  $Id: index.html,v 0.3 2017/09/11 08:43:43 dankogai Exp dankogai $
+  <div id="mocha"></div>
+</body>
+</html>

+ 25 - 0
js_sdk/js-base64/test/large.js

@@ -0,0 +1,25 @@
+/*
+ * $Id: large.js,v 0.3 2012/08/23 19:14:37 dankogai Exp dankogai $
+ *
+ * use mocha to test me
+ *   http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+var seed = function () {
+    var a, i;
+    for (a = [], i = 0; i < 256; i++) {
+        a.push(String.fromCharCode(i));
+    }
+    return a.join('');
+}();
+describe('Base64', function () {
+    for (var i = 0, str = seed; i < 16; str += str, i++) {
+        it(''+str.length, is(Base64.decode(Base64.encode(str)), str));
+    }
+});

+ 19 - 0
js_sdk/js-base64/test/yoshinoya.js

@@ -0,0 +1,19 @@
+/*
+ * use mocha to test me
+ * http://visionmedia.github.com/mocha/
+ */
+var assert = assert || require("assert");
+var Base64 = Base64 || require('../base64.js').Base64;
+var is = function (a, e, m) {
+    return function () {
+        assert.equal(a, e, m)
+    }
+};
+
+describe('Yoshinoya', function () {
+    it('.encode', is(Base64.encode('𠮷野家'), '8KCut+mHjuWutg=='));
+    it('.encodeURI', is(Base64.encodeURI('𠮷野家'), '8KCut-mHjuWutg'));
+    it('.decode', is(Base64.decode('8KCut+mHjuWutg=='), '𠮷野家'));
+    it('.decode', is(Base64.decode('8KCut-mHjuWutg'), '𠮷野家'));
+    /* it('.decode', is(Base64.decode('7aGC7b636YeO5a62'), '𠮷野家')); */
+});

+ 29 - 0
locale/de.json

@@ -0,0 +1,29 @@
+{
+	"locale.auto": "System",
+	"locale.en": "Englisch",
+	"locale.zh-hans": "Vereinfachtes Chinesisch",
+	"locale.zh-hant": "Traditionelles Chinesisch",
+	"locale.ja": "Japanisch",
+	"locale.de": "Deutsch",
+	"locale.es": "Spanisch",
+	"locale.sa": "Arabisch",
+	"locale.fr": "Französisch",
+	"index.title": "Hello i18n",
+	"index.home": "Startseite",
+	"index.component": "Komponente",
+	"index.api": "API",
+	"index.schema": "Schema",
+	"index.demo": "uni-app Internationalisierungs-Demonstration",
+	"index.demo-description": "Enthält uni-framework, manifest.json, pages.json, Tabbar, Seiten, Komponenten, API, Schema",
+	"index.detail": "Details",
+	"index.language": "Sprache",
+	"index.language-info": "Sprachinformation",
+	"index.system-language": "Systemsprache",
+	"index.application-language": "Anwendungssprache",
+	"index.language-change-confirm": "Die Anwendung dieser Einstellungen wird die App neu starten.",
+	"api.message": "Hinweis",
+	"schema.name": "Name",
+	"schema.add": "Hinzufügen",
+	"schema.add-success": "Erfolgreich hinzugefügt",
+	"my.tabbar": "Mein"
+}

+ 29 - 0
locale/en.json

@@ -0,0 +1,29 @@
+{
+	"locale.auto": "System",
+	"locale.en": "English",
+	"locale.zh-hans": "简体中文",
+	"locale.zh-hant": "繁体中文",
+	"locale.ja": "日语",
+	"locale.de": "German",
+	"locale.es": "Spanish",
+	"locale.sa": "Arabic",
+	"locale.fr": "French",
+	"index.title": "Hello i18n",
+	"index.home": "Home",
+	"index.component": "Component",
+	"index.api": "API",
+	"index.schema": "Schema",
+	"index.demo": "uni-app globalization",
+	"index.demo-description": "Include uni-framework, manifest.json, pages.json, tabbar, Page, Component, API, Schema",
+	"index.detail": "Detail",
+	"index.language": "Language",
+	"index.language-info": "Settings",
+	"index.system-language": "System language",
+	"index.application-language": "Application language",
+	"index.language-change-confirm": "Applying this setting will restart the app",
+	"api.message": "Message",
+	"schema.name": "Name",
+	"schema.add": "Add",
+	"schema.add-success": "Add success",
+	"my.tabbar": "my"
+}

+ 25 - 0
locale/es.json

@@ -0,0 +1,25 @@
+{
+	"locale.auto": "Sistema",
+	"locale.en": "Inglés",
+	"locale.zh-hans": "Chino simplificado",
+	"locale.zh-hant": "Chino tradicional",
+	"locale.ja": "Japonés",
+	"index.title": "Hola i18n",
+	"index.home": "Inicio",
+	"index.component": "Componente",
+	"index.api": "API",
+	"index.schema": "Esquema",
+	"index.demo": "Demostración de internacionalización de uni-app",
+	"index.demo-description": "Incluye uni-framework, manifest.json, pages.json, barra de pestañas, páginas, componentes, API, esquema",
+	"index.detail": "Detalles",
+	"index.language": "Idioma",
+	"index.language-info": "Información del idioma",
+	"index.system-language": "Idioma del sistema",
+	"index.application-language": "Idioma de la aplicación",
+	"index.language-change-confirm": "Aplicar esta configuración reiniciará la aplicación.",
+	"api.message": "Aviso",
+	"schema.name": "Nombre",
+	"schema.add": "Agregar",
+	"schema.add-success": "Agregado correctamente",
+	"my.tabbar": "Mío"
+}

+ 25 - 0
locale/fr.json

@@ -0,0 +1,25 @@
+{
+	"locale.auto": "Système",
+	"locale.en": "Anglais",
+	"locale.zh-hans": "Chinois simplifié",
+	"locale.zh-hant": "Chinois traditionnel",
+	"locale.ja": "Japonais",
+	"index.title": "Bonjour i18n",
+	"index.home": "Accueil",
+	"index.component": "Composant",
+	"index.api": "API",
+	"index.schema": "Schéma",
+	"index.demo": "Démonstration d'internationalisation de uni-app",
+	"index.demo-description": "Comprend uni-framework, manifest.json, pages.json, barre d'onglets, pages, composants, API, schéma",
+	"index.detail": "Détails",
+	"index.language": "Langue",
+	"index.language-info": "Informations sur la langue",
+	"index.system-language": "Langue du système",
+	"index.application-language": "Langue de l'application",
+	"index.language-change-confirm": "L'application de ces paramètres redémarrera l'application.",
+	"api.message": "Avertissement",
+	"schema.name": "Nom",
+	"schema.add": "Ajouter",
+	"schema.add-success": "Ajout réussi",
+	"my.tabbar": "Mon"
+}

+ 19 - 0
locale/index.js

@@ -0,0 +1,19 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+import ja from './ja.json'
+import de from './de.json'
+import fr from './fr.json'
+import es from './es.json'
+import sa from './sa.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant,
+	ja,
+	de,
+	fr,
+	es,
+	sa
+
+}

+ 28 - 0
locale/ja.json

@@ -0,0 +1,28 @@
+{
+	"locale.auto": "システム",
+	"locale.en": "英語",
+	"locale.zh-hans": "简体中文",
+	"locale.zh-hant": "繁体中文",
+	"locale.ja": "日语",
+	"locale.de": " ドイツ語 ",
+	"locale.es": "スペイン語",
+	"locale.sa": "アラビア語",
+	"locale.fr": "フランス語"
+	"index.title": "Hello i18n",
+	"index.home": "ホーム",
+	"index.component": "コンポーネント",
+	"index.api": "API",
+	"index.schema": "Schema",
+	"index.demo": "uni-app globalization",
+	"index.demo-description": "ユニフレームワーク、manifest.json、pages.json、タブバー、ページ、コンポーネント、APIを含める、Schema",
+	"index.detail": "詳細",
+	"index.language": "言語",
+	"index.language-info": "設定",
+	"index.system-language": "システム言語",
+	"index.application-language": "アプリケーション言語",
+	"index.language-change-confirm": "この設定を適用すると、アプリが再起動します",
+	"api.message": "メッセージ",
+	"schema.add": "追加",
+	"schema.add-success": "成功を追加"
+	"my.tabbar": "わたし"
+}

+ 29 - 0
locale/sa.json

@@ -0,0 +1,29 @@
+{
+	"locale.auto": "النظام",
+	"locale.en": "اللغة الإنجليزية",
+	"locale.zh-hans": "اللغة الصينية المبسطة",
+	"locale.zh-hant": "اللغة الصينية التقليدية",
+	"locale.ja": "اللغة اليابانية",
+	"locale.de": "اللغة الألمانية",
+	"locale.es": "اللغة الإسبانية",
+	"locale.sa": "اللغة العربية",
+	"locale.fr": "اللغة الفرنسية",
+	"index.title": "مرحبًا ب i18n",
+	"index.home": "الصفحة الرئيسية",
+	"index.component": "المكون",
+	"index.api": "API",
+	"index.schema": "_SCHEMA",
+	"index.demo": "демو التنميّز باللغات لـ uni-app",
+	"index.demo-description": "محتوي على uni-framework، manifest.json، pages.json، ع栏 bar، الصفحات، المكونات، API، SCHEMA",
+	"index.detail": "التفاصيل",
+	"index.language": "اللغة",
+	"index.language-info": "معلومات اللغة",
+	"index.system-language": "لغة النظام",
+	"index.application-language": "لغة التطبيق",
+	"index.language-change-confirm": "سيؤدي تطبيق هذه الإعدادات إلى إعادة تشغيل التطبيق.",
+	"api.message": "نصيحة",
+	"schema.name": "الاسم",
+	"schema.add": "إضافة",
+	"schema.add-success": "تمت الإضافة بنجاح",
+	"my.tabbar": "مصاريفي"
+}

+ 36 - 0
locale/uni-app.ja.json

@@ -0,0 +1,36 @@
+{
+  "common": {
+    "uni.app.quit": "もう一度押すと、アプリケーションが終了します",
+    "uni.async.error": "サーバーへの接続がタイムアウトしました。画面をクリックして再試行してください",
+    "uni.showActionSheet.cancel": "キャンセル",
+    "uni.showToast.unpaired": "使用するには、showToastとhideToastをペアにする必要があることに注意してください",
+    "uni.showLoading.unpaired": "使用するには、showLoadingとhideLoadingをペアにする必要があることに注意してください",
+    "uni.showModal.cancel": "キャンセル",
+    "uni.showModal.confirm": "OK",
+    "uni.chooseImage.cancel": "キャンセル",
+    "uni.chooseImage.sourceType.album": "アルバムから選択",
+    "uni.chooseImage.sourceType.camera": "カメラ",
+    "uni.chooseVideo.cancel": "キャンセル",
+    "uni.chooseVideo.sourceType.album": "アルバムから選択",
+    "uni.chooseVideo.sourceType.camera": "カメラ",
+    "uni.previewImage.cancel": "キャンセル",
+    "uni.previewImage.button.save": "画像を保存",
+    "uni.previewImage.save.success": "画像をアルバムに正常に保存します",
+    "uni.previewImage.save.fail": "画像をアルバムに保存できませんでした",
+    "uni.setClipboardData.success": "コンテンツがコピーされました",
+    "uni.scanCode.title": "スキャンコード",
+    "uni.scanCode.album": "アルバム",
+    "uni.scanCode.fail": "認識に失敗しました",
+    "uni.scanCode.flash.on": "タッチして点灯",
+    "uni.scanCode.flash.off": "タップして閉じる",
+    "uni.startSoterAuthentication.authContent": "指紋認識...",
+    "uni.picker.done": "完了",
+    "uni.picker.cancel": "キャンセル",
+    "uni.video.danmu": "「弾幕」",
+    "uni.video.volume": "ボリューム",
+    "uni.button.feedback.title": "質問のフィードバック",
+    "uni.button.feedback.send": "送信"
+  },
+  "ios": {},
+  "android": {}
+}

+ 29 - 0
locale/zh-Hans.json

@@ -0,0 +1,29 @@
+{
+	"locale.auto": "系统",
+	"locale.en": "English",
+	"locale.zh-hans": "简体中文",
+	"locale.zh-hant": "繁体中文",
+	"locale.ja": "日语",
+	"locale.de": "德语",
+	"locale.es": "西班牙语",
+	"locale.sa": "阿拉伯语",
+	"locale.fr": "法语",
+	"index.title": "Hello i18n",
+	"index.home": "主页",
+	"index.component": "组件",
+	"index.api": "API",
+	"index.schema": "Schema",
+	"index.demo": "uni-app 国际化演示",
+	"index.demo-description": "包含 uni-framework、manifest.json、pages.json、tabbar、页面、组件、API、Schema",
+	"index.detail": "详情",
+	"index.language": "语言",
+	"index.language-info": "语言信息",
+	"index.system-language": "系统语言",
+	"index.application-language": "应用语言",
+	"index.language-change-confirm": "应用此设置将重启App",
+	"api.message": "提示",
+	"schema.name": "姓名",
+	"schema.add": "新增",
+	"schema.add-success": "新增成功",
+	"my.tabbar": "我的"
+}

+ 28 - 0
locale/zh-Hant.json

@@ -0,0 +1,28 @@
+{
+	"locale.auto": "系統",
+	"locale.en": "English",
+	"locale.zh-hans": "简体中文",
+	"locale.zh-hant": "繁體中文",
+	"locale.de": "德语",
+	"locale.es": "西班牙语",
+	"locale.sa": "阿拉伯语",
+	"locale.fr": "法语",
+	"locale.ja": "日语",
+	"index.title": "Hello i18n",
+	"index.home": "主頁",
+	"index.component": "組件",
+	"index.api": "API",
+	"index.schema": "Schema",
+	"index.demo": "uni-app 國際化演示",
+	"index.demo-description": "包含 uni-framework、manifest.json、pages.json、tabbar、頁面、組件、API、Schema",
+	"index.detail": "詳情",
+	"index.language": "語言",
+	"index.language-info": "語言信息",
+	"index.system-language": "系統語言",
+	"index.application-language": "應用語言",
+	"index.language-change-confirm": "應用此設置將重啟App",
+	"api.message": "提示",
+	"schema.name": "姓名",
+	"schema.add": "新增",
+	"schema.add-success": "新增成功"
+}

+ 73 - 0
main.js

@@ -0,0 +1,73 @@
+import App from './App'
+import uviewPlus from '@/uni_modules/uview-plus'
+import store from './store/index.js'
+import {
+	initRequest
+} from '@/config/request/index'
+import messages from './locale/index'
+import {
+	route
+} from '@/uni_modules/uview-plus'
+
+let i18nConfig = {
+	locale: uni.getLocale(),
+	messages
+}
+
+// #ifndef VUE3
+import Vue from 'vue'
+import VueI18n from 'vue-i18n'
+Vue.use(VueI18n)
+
+// 全局混入 route 方法
+Vue.mixin({
+	methods: {
+		route(path) {
+			route(path)
+		}
+	}
+})
+
+const vue2I18n = new VueI18n(i18nConfig)
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+	i18n: vue2I18n,
+	...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import {
+	createSSRApp
+} from 'vue'
+import {
+	createI18n
+} from 'vue-i18n'
+
+const vue3I18n = createI18n(i18nConfig)
+export function createApp() {
+	const app = createSSRApp(App)
+	app.use(vue3I18n)
+	app.use(uviewPlus)
+	app.use(store)
+	initRequest(app)
+
+	// 添加全局方法
+	app.config.globalProperties.$route = route
+
+	// 添加全局混入
+	app.mixin({
+		methods: {
+			$route(path) {
+				route(path)
+			}
+		}
+	})
+
+	return {
+		app
+	}
+}
+// #endif

+ 121 - 0
manifest.json

@@ -0,0 +1,121 @@
+{
+    "name" : "hjmy-app",
+    "appid" : "__UNI__68AB107",
+    "description" : "",
+    "versionName" : "1.0.1",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {
+            "Bluetooth" : {},
+            "Camera" : {},
+            "VideoPlayer" : {},
+            "Payment" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false
+            },
+            /* SDK配置 */
+            "sdkConfigs" : {
+                "payment" : {},
+                "push" : {
+                    "unipush" : {}
+                }
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            }
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx13733f809956c2c1",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true,
+        // 添加这一行
+        "requiredPrivateInfos" : [ "chooseAddress" ]
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "3",
+    "h5" : {
+        "title" : "宏匠唐卡"
+    }
+}

+ 2181 - 0
package-lock.json

@@ -0,0 +1,2181 @@
+{
+  "name": "hello-i18n",
+  "version": "1.0.2",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "hello-i18n",
+      "version": "1.0.2",
+      "dependencies": {
+        "clipboard": "^2.0.11",
+        "crypto-js": "^4.2.0",
+        "dayjs": "^1.11.13",
+        "js-base64": "^3.7.7",
+        "mqtt": "^5.10.4",
+        "uni-simple-router": "^2.0.8-beta.4"
+      },
+      "devDependencies": {
+        "sass": "^1.63.2",
+        "sass-loader": "^10.4.1"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.28.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
+      "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.12",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+      "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.10",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
+      "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+      "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.29",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+      "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@parcel/watcher": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+      "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^1.0.3",
+        "is-glob": "^4.0.3",
+        "micromatch": "^4.0.5",
+        "node-addon-api": "^7.0.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher-android-arm64": "2.5.1",
+        "@parcel/watcher-darwin-arm64": "2.5.1",
+        "@parcel/watcher-darwin-x64": "2.5.1",
+        "@parcel/watcher-freebsd-x64": "2.5.1",
+        "@parcel/watcher-linux-arm-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm-musl": "2.5.1",
+        "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+        "@parcel/watcher-linux-arm64-musl": "2.5.1",
+        "@parcel/watcher-linux-x64-glibc": "2.5.1",
+        "@parcel/watcher-linux-x64-musl": "2.5.1",
+        "@parcel/watcher-win32-arm64": "2.5.1",
+        "@parcel/watcher-win32-ia32": "2.5.1",
+        "@parcel/watcher-win32-x64": "2.5.1"
+      }
+    },
+    "node_modules/@parcel/watcher-android-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+      "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+      "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-darwin-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+      "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-freebsd-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+      "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+      "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+      "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+      "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-arm64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+      "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-glibc": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+      "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-linux-x64-musl": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+      "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-arm64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+      "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-ia32": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+      "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@parcel/watcher-win32-x64": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+      "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/@types/eslint": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+      "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/estree": "*",
+        "@types/json-schema": "*"
+      }
+    },
+    "node_modules/@types/eslint-scope": {
+      "version": "3.7.7",
+      "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+      "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/eslint": "*",
+        "@types/estree": "*"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "24.1.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
+      "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
+      "dependencies": {
+        "undici-types": "~7.8.0"
+      }
+    },
+    "node_modules/@types/readable-stream": {
+      "version": "4.0.21",
+      "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.21.tgz",
+      "integrity": "sha512-19eKVv9tugr03IgfXlA9UVUVRbW6IuqRO5B92Dl4a6pT7K8uaGrNS0GkxiZD0BOk6PLuXl5FhWl//eX/pzYdTQ==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/ws": {
+      "version": "8.18.1",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@webassemblyjs/ast": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
+      "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/helper-numbers": "1.13.2",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
+      "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-api-error": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
+      "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-buffer": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
+      "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-numbers": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
+      "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+        "@webassemblyjs/helper-api-error": "1.13.2",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
+      "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/helper-wasm-section": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
+      "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/wasm-gen": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/ieee754": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
+      "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "node_modules/@webassemblyjs/leb128": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
+      "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@webassemblyjs/utf8": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
+      "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@webassemblyjs/wasm-edit": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
+      "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/helper-wasm-section": "1.14.1",
+        "@webassemblyjs/wasm-gen": "1.14.1",
+        "@webassemblyjs/wasm-opt": "1.14.1",
+        "@webassemblyjs/wasm-parser": "1.14.1",
+        "@webassemblyjs/wast-printer": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-gen": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
+      "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/ieee754": "1.13.2",
+        "@webassemblyjs/leb128": "1.13.2",
+        "@webassemblyjs/utf8": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-opt": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
+      "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-buffer": "1.14.1",
+        "@webassemblyjs/wasm-gen": "1.14.1",
+        "@webassemblyjs/wasm-parser": "1.14.1"
+      }
+    },
+    "node_modules/@webassemblyjs/wasm-parser": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
+      "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@webassemblyjs/helper-api-error": "1.13.2",
+        "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+        "@webassemblyjs/ieee754": "1.13.2",
+        "@webassemblyjs/leb128": "1.13.2",
+        "@webassemblyjs/utf8": "1.13.2"
+      }
+    },
+    "node_modules/@webassemblyjs/wast-printer": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
+      "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@webassemblyjs/ast": "1.14.1",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "node_modules/@xtuc/ieee754": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/@xtuc/long": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/abort-controller": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+      "dependencies": {
+        "event-target-shim": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6.5"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.15.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+      "dev": true,
+      "peer": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-import-phases": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+      "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "peerDependencies": {
+        "acorn": "^8.14.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ajv-formats": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+      "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "ajv": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/ajv-formats/node_modules/ajv": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ajv-formats/node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/ajv-keywords": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+      "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+      "dev": true,
+      "peerDependencies": {
+        "ajv": "^6.9.1"
+      }
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/big.js": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+      "dev": true,
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/bl": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.1.tgz",
+      "integrity": "sha512-yYc8UIHrd1ZTLgNBIE7JjMzUPZH+dec3q7nWkrSHEbtvkQ3h6WKC63W9K5jthcL5EXFyMuWYq+2pq5WMSIgFHw==",
+      "dependencies": {
+        "@types/readable-stream": "^4.0.0",
+        "buffer": "^6.0.3",
+        "inherits": "^2.0.4",
+        "readable-stream": "^4.2.0"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/broker-factory": {
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.8.tgz",
+      "integrity": "sha512-xmVnYN0FZtynhPUmAnN+/MFRdbDi3syCuxWV7o7s78FcIN0pjDtn9mUrVqEgdjQkbfojRhlPWbYbXJkMCyddrg==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "fast-unique-numbers": "^9.0.22",
+        "tslib": "^2.8.1",
+        "worker-factory": "^7.0.44"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.25.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+      "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "peer": true,
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001726",
+        "electron-to-chromium": "^1.5.173",
+        "node-releases": "^2.0.19",
+        "update-browserslist-db": "^1.1.3"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001731",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
+      "integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "peer": true
+    },
+    "node_modules/chokidar": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+      "dev": true,
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/chrome-trace-event": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+      "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6.0"
+      }
+    },
+    "node_modules/clipboard": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
+    "node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/commist": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz",
+      "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw=="
+    },
+    "node_modules/concat-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+      "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+      "engines": [
+        "node >= 6.0"
+      ],
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.0.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "node_modules/concat-stream/node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.13",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+    },
+    "node_modules/debug": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+    },
+    "node_modules/detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "dev": true,
+      "optional": true,
+      "bin": {
+        "detect-libc": "bin/detect-libc.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.194",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.194.tgz",
+      "integrity": "sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/emojis-list": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/enhanced-resolve": {
+      "version": "5.18.2",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
+      "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/es-module-lexer": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+      "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esrecurse/node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/event-target-shim": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/events": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+      "engines": {
+        "node": ">=0.8.x"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-unique-numbers": {
+      "version": "9.0.22",
+      "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.22.tgz",
+      "integrity": "sha512-dBR+30yHAqBGvOuxxQdnn2lTLHCO6r/9B+M4yF8mNrzr3u1yiF+YVJ6u3GTyPN/VRWqaE1FcscZDdBgVKmrmQQ==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "tslib": "^2.8.1"
+      },
+      "engines": {
+        "node": ">=18.2.0"
+      }
+    },
+    "node_modules/fast-uri": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
+      "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fastify"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/fastify"
+        }
+      ],
+      "peer": true
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/glob-to-regexp": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+      "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/help-me": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+      "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/immutable": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
+      "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+      "dev": true
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "node_modules/ip-address": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+      "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
+      "dependencies": {
+        "jsbn": "1.1.0",
+        "sprintf-js": "^1.1.3"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/jest-worker": {
+      "version": "27.5.1",
+      "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+      "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/node": "*",
+        "merge-stream": "^2.0.0",
+        "supports-color": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      }
+    },
+    "node_modules/js-base64": {
+      "version": "3.7.7",
+      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz",
+      "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
+    },
+    "node_modules/js-sdsl": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
+      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/js-sdsl"
+      }
+    },
+    "node_modules/jsbn": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+      "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
+    },
+    "node_modules/json-parse-even-better-errors": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/klona": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
+      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/loader-runner": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+      "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6.11.5"
+      }
+    },
+    "node_modules/loader-utils": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+      "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+      "dev": true,
+      "dependencies": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^2.1.2"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
+    },
+    "node_modules/merge-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mqtt": {
+      "version": "5.14.0",
+      "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.0.tgz",
+      "integrity": "sha512-H7EmeCJhbGblbWjm6APF5sAH3SkdI7lxHw/UkblZp8fjSNl8b2MsLcdAkIaQKxvZYmiORkdAjffvKjqQWPkd6w==",
+      "dependencies": {
+        "@types/readable-stream": "^4.0.21",
+        "@types/ws": "^8.18.1",
+        "commist": "^3.2.0",
+        "concat-stream": "^2.0.0",
+        "debug": "^4.4.1",
+        "help-me": "^5.0.0",
+        "lru-cache": "^10.4.3",
+        "minimist": "^1.2.8",
+        "mqtt-packet": "^9.0.2",
+        "number-allocator": "^1.0.14",
+        "readable-stream": "^4.7.0",
+        "rfdc": "^1.4.1",
+        "socks": "^2.8.6",
+        "split2": "^4.2.0",
+        "worker-timers": "^8.0.23",
+        "ws": "^8.18.3"
+      },
+      "bin": {
+        "mqtt": "build/bin/mqtt.js",
+        "mqtt_pub": "build/bin/pub.js",
+        "mqtt_sub": "build/bin/sub.js"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/mqtt-packet": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz",
+      "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==",
+      "dependencies": {
+        "bl": "^6.0.8",
+        "debug": "^4.3.4",
+        "process-nextick-args": "^2.0.1"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/neo-async": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+      "dev": true
+    },
+    "node_modules/node-addon-api": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+      "dev": true,
+      "optional": true
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.19",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/number-allocator": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz",
+      "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
+      "dependencies": {
+        "debug": "^4.3.1",
+        "js-sdsl": "4.3.0"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
+      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
+      "dependencies": {
+        "abort-controller": "^3.0.0",
+        "buffer": "^6.0.3",
+        "events": "^3.3.0",
+        "process": "^0.11.10",
+        "string_decoder": "^1.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 14.18.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/require-from-string": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rfdc": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+      "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/sass": {
+      "version": "1.89.2",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz",
+      "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^4.0.0",
+        "immutable": "^5.0.2",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "optionalDependencies": {
+        "@parcel/watcher": "^2.4.1"
+      }
+    },
+    "node_modules/sass-loader": {
+      "version": "10.5.2",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.5.2.tgz",
+      "integrity": "sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ==",
+      "dev": true,
+      "dependencies": {
+        "klona": "^2.0.4",
+        "loader-utils": "^2.0.0",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^3.0.0",
+        "semver": "^7.3.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "fibers": ">= 3.1.0",
+        "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
+        "sass": "^1.3.0",
+        "webpack": "^4.36.0 || ^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "fibers": {
+          "optional": true
+        },
+        "node-sass": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/schema-utils": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.8",
+        "ajv": "^6.12.5",
+        "ajv-keywords": "^3.5.2"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
+    },
+    "node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/serialize-javascript": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "node_modules/smart-buffer": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+      "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+      "engines": {
+        "node": ">= 6.0.0",
+        "npm": ">= 3.0.0"
+      }
+    },
+    "node_modules/socks": {
+      "version": "2.8.6",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz",
+      "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==",
+      "dependencies": {
+        "ip-address": "^9.0.5",
+        "smart-buffer": "^4.2.0"
+      },
+      "engines": {
+        "node": ">= 10.0.0",
+        "npm": ">= 3.0.0"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
+    "node_modules/sprintf-js": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+      "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/tapable": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+      "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/terser": {
+      "version": "5.43.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
+      "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.14.0",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/terser-webpack-plugin": {
+      "version": "5.3.14",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
+      "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "^0.3.25",
+        "jest-worker": "^27.4.5",
+        "schema-utils": "^4.3.0",
+        "serialize-javascript": "^6.0.2",
+        "terser": "^5.31.1"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependencies": {
+        "webpack": "^5.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "esbuild": {
+          "optional": true
+        },
+        "uglify-js": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/terser-webpack-plugin/node_modules/ajv": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+      "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3"
+      },
+      "peerDependencies": {
+        "ajv": "^8.8.2"
+      }
+    },
+    "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
+      "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.9",
+        "ajv": "^8.9.0",
+        "ajv-formats": "^2.1.1",
+        "ajv-keywords": "^5.1.0"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "optional": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+    },
+    "node_modules/typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+    },
+    "node_modules/undici-types": {
+      "version": "7.8.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+      "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="
+    },
+    "node_modules/uni-simple-router": {
+      "version": "2.0.8-beta.4",
+      "resolved": "https://registry.npmjs.org/uni-simple-router/-/uni-simple-router-2.0.8-beta.4.tgz",
+      "integrity": "sha512-ipTHhOaRvjV8qrt3HosX5pNMhwFYBnFOuKyV5joH0evfXubjrGI5tjdwpmwzfW5h3VBth3iAqScv+pW/QmIJXw=="
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "peer": true,
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+    },
+    "node_modules/watchpack": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
+      "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "glob-to-regexp": "^0.4.1",
+        "graceful-fs": "^4.1.2"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/webpack": {
+      "version": "5.101.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
+      "integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/eslint-scope": "^3.7.7",
+        "@types/estree": "^1.0.8",
+        "@types/json-schema": "^7.0.15",
+        "@webassemblyjs/ast": "^1.14.1",
+        "@webassemblyjs/wasm-edit": "^1.14.1",
+        "@webassemblyjs/wasm-parser": "^1.14.1",
+        "acorn": "^8.15.0",
+        "acorn-import-phases": "^1.0.3",
+        "browserslist": "^4.24.0",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^5.17.2",
+        "es-module-lexer": "^1.2.1",
+        "eslint-scope": "5.1.1",
+        "events": "^3.2.0",
+        "glob-to-regexp": "^0.4.1",
+        "graceful-fs": "^4.2.11",
+        "json-parse-even-better-errors": "^2.3.1",
+        "loader-runner": "^4.2.0",
+        "mime-types": "^2.1.27",
+        "neo-async": "^2.6.2",
+        "schema-utils": "^4.3.2",
+        "tapable": "^2.1.1",
+        "terser-webpack-plugin": "^5.3.11",
+        "watchpack": "^2.4.1",
+        "webpack-sources": "^3.3.3"
+      },
+      "bin": {
+        "webpack": "bin/webpack.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      },
+      "peerDependenciesMeta": {
+        "webpack-cli": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/webpack-sources": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
+      "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/webpack/node_modules/ajv": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/webpack/node_modules/ajv-keywords": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+      "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3"
+      },
+      "peerDependencies": {
+        "ajv": "^8.8.2"
+      }
+    },
+    "node_modules/webpack/node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/webpack/node_modules/schema-utils": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
+      "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.9",
+        "ajv": "^8.9.0",
+        "ajv-formats": "^2.1.1",
+        "ajv-keywords": "^5.1.0"
+      },
+      "engines": {
+        "node": ">= 10.13.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/webpack"
+      }
+    },
+    "node_modules/worker-factory": {
+      "version": "7.0.44",
+      "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.44.tgz",
+      "integrity": "sha512-08AuUfWi+KeZI+KC7nU4pU/9tDeAFvE5NSWk+K9nIfuQc6UlOsZtjjeGVYVEn+DEchyXNJ5i10HCn0xRzFXEQA==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "fast-unique-numbers": "^9.0.22",
+        "tslib": "^2.8.1"
+      }
+    },
+    "node_modules/worker-timers": {
+      "version": "8.0.23",
+      "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.23.tgz",
+      "integrity": "sha512-1BnWHNNiu5YEutgF7eVZEqNntAsij2oG0r66xDdScoY3fKGFrok2y0xA8OgG6FA+3srrmAplSY6JN5h9jV5D0w==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "tslib": "^2.8.1",
+        "worker-timers-broker": "^8.0.9",
+        "worker-timers-worker": "^9.0.9"
+      }
+    },
+    "node_modules/worker-timers-broker": {
+      "version": "8.0.9",
+      "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.9.tgz",
+      "integrity": "sha512-WJsd7aIvu2GBTXp7IBGT1NKnt3ZbiJ2wqb7Pl4nFJXC8pek84+X68TJGVvvrqwHgHPNxSlzpU1nadhcW4PDD7A==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "broker-factory": "^3.1.8",
+        "fast-unique-numbers": "^9.0.22",
+        "tslib": "^2.8.1",
+        "worker-timers-worker": "^9.0.9"
+      }
+    },
+    "node_modules/worker-timers-worker": {
+      "version": "9.0.9",
+      "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.9.tgz",
+      "integrity": "sha512-OOKTMdHbzx7FaXCW40RS8RxAqLF/R8xU5/YA7CFasDy+jBA5yQWUusSQJUFFTV2Z9ZOpnR+ZWgte/IuAqOAEVw==",
+      "dependencies": {
+        "@babel/runtime": "^7.27.6",
+        "tslib": "^2.8.1",
+        "worker-factory": "^7.0.44"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.18.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+      "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    }
+  }
+}

+ 91 - 0
package.json

@@ -0,0 +1,91 @@
+{
+  "id": "uni-hello-i18n",
+  "name": "hello-i18n",
+  "displayName": "hello-i18n 示例工程",
+  "version": "1.0.2",
+  "description": "uni-app 国际化演示",
+  "keywords": [
+    "i18n",
+    "hello-i18n",
+    "多语言"
+  ],
+  "dcloudext": {
+    "category": [
+      "前端页面模板",
+      "uni-app前端项目模板"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "repository": "",
+  "uni_modules": {
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "u",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "u",
+          "Android Browser": "u",
+          "微信浏览器(Android)": "u",
+          "QQ浏览器(Android)": "u"
+        },
+        "H5-pc": {
+          "Chrome": "u",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "u",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+          "vue2": "u",
+          "vue3": "u"
+        }
+      }
+    }
+  },
+  "devDependencies": {
+    "sass": "^1.63.2",
+    "sass-loader": "^10.4.1"
+  },
+  "dependencies": {
+    "clipboard": "^2.0.11",
+    "crypto-js": "^4.2.0",
+    "dayjs": "^1.11.13",
+    "js-base64": "^3.7.7",
+    "mqtt": "^5.10.4",
+    "uni-simple-router": "^2.0.8-beta.4"
+  }
+}

+ 262 - 0
packageOrder/pages/after-sale/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <view class="after-sale">
+    <!-- 商品信息 -->
+    <view class="product-info">
+      <image :src="product.image" mode="aspectFill" class="product-image"></image>
+      <view class="product-detail">
+        <view class="product-name">{{product.name}}</view>
+        <view class="product-spec">{{product.spec}}</view>
+        <view class="product-price">¥{{product.price}}</view>
+      </view>
+    </view>
+    
+    <!-- 售后表单 -->
+    <view class="form-section">
+      <u-form :model="form" ref="form">
+        <u-form-item label="售后类型" prop="type" required>
+          <u-radio-group v-model="form.type">
+            <u-radio 
+              v-for="(item, index) in typeOptions" 
+              :key="index"
+              :name="item.value"
+              :label="item.label">
+            </u-radio>
+          </u-radio-group>
+        </u-form-item>
+        
+        <u-form-item label="申请原因" prop="reason" required>
+          <u-select
+            v-model="form.reason"
+            :list="reasonOptions"
+            placeholder="请选择申请原因">
+          </u-select>
+        </u-form-item>
+        
+        <u-form-item label="问题描述" prop="description" required>
+          <u-textarea
+            v-model="form.description"
+            placeholder="请详细描述您遇到的问题"
+            count
+            maxlength="500">
+          </u-textarea>
+        </u-form-item>
+        
+        <u-form-item label="上传凭证">
+          <u-upload
+            :fileList="form.images"
+            @afterRead="afterRead"
+            @delete="deletePic"
+            name="1"
+            multiple
+            maxCount="9">
+          </u-upload>
+        </u-form-item>
+        
+        <u-form-item label="联系人" prop="contact" required>
+          <u-input v-model="form.contact" placeholder="请输入联系人姓名" />
+        </u-form-item>
+        
+        <u-form-item label="手机号码" prop="phone" required>
+          <u-input v-model="form.phone" placeholder="请输入手机号码" type="number" maxlength="11" />
+        </u-form-item>
+      </u-form>
+    </view>
+    
+    <!-- 提交按钮 -->
+    <view class="submit-btn">
+      <u-button type="primary" @click="submit" color="#D93025">提交申请</u-button>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      orderId: '',
+      productId: '',
+      product: {
+        image: '',
+        name: '',
+        spec: '',
+        price: ''
+      },
+      form: {
+        type: 'refund',
+        reason: '',
+        description: '',
+        images: [],
+        contact: '',
+        phone: ''
+      },
+      typeOptions: [
+        { label: '仅退款', value: 'refund' },
+        { label: '退货退款', value: 'return' }
+      ],
+      reasonOptions: [
+        { label: '商品质量问题', value: 'quality' },
+        { label: '商品损坏', value: 'damaged' },
+        { label: '商品与描述不符', value: 'mismatch' },
+        { label: '收到错误商品', value: 'wrong' },
+        { label: '其他原因', value: 'other' }
+      ],
+      rules: {
+        type: [{
+          required: true,
+          message: '请选择售后类型',
+          trigger: ['change']
+        }],
+        reason: [{
+          required: true,
+          message: '请选择申请原因',
+          trigger: ['change']
+        }],
+        description: [{
+          required: true,
+          message: '请输入问题描述',
+          trigger: ['blur']
+        }],
+        contact: [{
+          required: true,
+          message: '请输入联系人姓名',
+          trigger: ['blur']
+        }],
+        phone: [{
+          required: true,
+          message: '请输入手机号码',
+          trigger: ['blur']
+        }, {
+          pattern: /^1[3-9]\d{9}$/,
+          message: '请输入正确的手机号码',
+          trigger: ['blur']
+        }]
+      }
+    }
+  },
+  onLoad(options) {
+    this.orderId = options.orderId
+    this.productId = options.productId
+    this.getProductInfo()
+  },
+  methods: {
+    async getProductInfo() {
+      try {
+        const res = await this.$api.product.detail(this.productId)
+        this.product = res.data
+      } catch (e) {
+        this.$u.toast('获取商品信息失败')
+      }
+    },
+    async afterRead(event) {
+      const { file } = event
+      const uploadPromises = (Array.isArray(file) ? file : [file]).map(item => {
+        return this.uploadFilePromise(item)
+      })
+
+      try {
+        const urls = await Promise.all(uploadPromises)
+        this.form.images = [...this.form.images, ...urls]
+      } catch (e) {
+        this.$u.toast('上传图片失败')
+      }
+    },
+    uploadFilePromise(file) {
+      return new Promise((resolve, reject) => {
+        uni.uploadFile({
+          url: this.$api.common.uploadUrl,
+          filePath: file.url,
+          name: 'file',
+          success: (res) => {
+            const data = JSON.parse(res.data)
+            resolve(data.url)
+          },
+          fail: reject
+        })
+      })
+    },
+    deletePic(event) {
+      const index = event.index
+      this.form.images.splice(index, 1)
+    },
+    async submit() {
+      try {
+        await this.$refs.form.validate()
+        await this.$api.order.afterSale({
+          orderId: this.orderId,
+          productId: this.productId,
+          ...this.form
+        })
+        this.$u.toast('提交成功')
+        setTimeout(() => {
+          uni.navigateBack()
+        }, 1500)
+      } catch (e) {
+        if (e.errors) return
+        this.$u.toast('提交失败')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.after-sale {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 120rpx;
+  
+  .product-info {
+    background: #fff;
+    padding: 30rpx;
+    display: flex;
+    margin-bottom: 20rpx;
+    
+    .product-image {
+      width: 160rpx;
+      height: 160rpx;
+      border-radius: 8rpx;
+      margin-right: 20rpx;
+    }
+    
+    .product-detail {
+      flex: 1;
+      
+      .product-name {
+        font-size: 28rpx;
+        color: #333;
+        margin-bottom: 10rpx;
+      }
+      
+      .product-spec {
+        font-size: 24rpx;
+        color: #999;
+        margin-bottom: 20rpx;
+      }
+      
+      .product-price {
+        font-size: 32rpx;
+        color: #D93025;
+        font-weight: bold;
+      }
+    }
+  }
+  
+  .form-section {
+    background: #fff;
+    padding: 30rpx;
+    
+    :deep(.u-form-item) {
+      padding: 20rpx 0;
+    }
+  }
+  
+  .submit-btn {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    padding: 20rpx 40rpx;
+    background: #fff;
+  }
+}
+</style> 

+ 877 - 0
packageOrder/pages/detail/index.vue

@@ -0,0 +1,877 @@
+<template>
+	<view class="order-detail">
+		<!-- 订单状态 -->
+		<view class="status-section" :class="getStatusClass()">
+			<view class="status-bg-pattern"></view>
+			<view class="logistics-btn" v-if="[3,4].includes(order.status) && order.expressCode">
+				<button class="custom-logistics-btn" @click="viewLogistics">
+					<image src="/static/images/order/wuliu.png" mode="aspectFit" class="logistics-icon"></image>
+					<text>查看物流</text>
+				</button>
+			</view>
+			<view class="status-content">
+				<view class="status-icon">
+					<image :src="getStatusImage()" mode="aspectFit" class="status-img"></image>
+				</view>
+				<view class="status-info">
+					<view class="status-text">{{getStatusText()}}</view>
+					<view class="status-desc">{{getStatusDesc()}}</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 收货地址和物流信息 -->
+		<view class="address-section">
+			<view class="section-header">
+				<view class="header-icon">
+					<uni-icons type="map-pin-ellipse" size="25" color="#a4d5d9"></uni-icons>
+				</view>
+				<text class="header-text">收货信息</text>
+			</view>
+			<view class="address-content">
+				<view class="user-info">
+					<text class="name">{{order.name}}</text>
+					<text class="phone">{{order.mobile}}</text>
+				</view>
+				<view class="address">
+					{{order.province}}{{order.city}}{{order.area}}{{order.address}}
+				</view>
+				<!-- 物流信息 -->
+				<view class="logistics-info" v-if="order.expressCompany && order.expressCode">
+					<view class="divider"></view>
+					<view class="logistics-item">
+						<text class="label">快递公司</text>
+						<text class="value">{{order.expressCompany}}</text>
+					</view>
+					<view class="logistics-item">
+						<text class="label">快递单号</text>
+						<view class="value-copy">
+							<text class="value">{{order.expressCode}}</text>
+							<button class="copy-btn" @click="copyExpressCode">复制</button>
+						</view>
+					</view>
+					<view class="logistics-item" v-if="order.shipTime">
+						<text class="label">发货时间</text>
+						<text class="value">{{order.shipTime}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 商品信息 -->
+		<view class="product-section">
+			<view class="section-header">
+				<view class="header-icon">
+					<uni-icons type="gift-filled" size="25" color="#a4d5d9"></uni-icons>
+				</view>
+				<text class="header-text">商品信息</text>
+			</view>
+			<view class="product-list">
+				<view v-for="(item, index) in orderListVos" :key="index" class="product-item">
+					<view class="product-image-wrapper" @tap="goDetail(item)">
+						<image :src="item.productImage" mode="aspectFill" class="product-image"></image>
+					</view>
+					<view class="product-info" @tap="goDetail(item)">
+						<view class="product-name">{{item.productName}}</view>
+						<view class="product-spec" v-if="item.skuName">{{item.skuName}}</view>
+						<view class="product-price-count">
+							<view class="price-info">
+								<text class="price">¥{{item.unitPrice}}</text>
+								<text class="subtotal">小计:¥{{item.subtotalAmount}}</text>
+							</view>
+							<text class="count">x{{item.quantity}}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+
+		<!-- 金额信息 -->
+		<view class="amount-section">
+			<view class="section-header">
+				<view class="header-icon">
+					<u-icon name="file-text-fill" size="25" color="#a4d5d9"></u-icon>
+				</view>
+				<text class="header-text">金额信息</text>
+			</view>
+			<view class="amount-content">
+				<view class="amount-item">
+					<text>商品总额</text>
+					<text class="price">¥{{getTotalProductPrice()}}</text>
+				</view>
+				<view class="amount-item total">
+					<text>实付款</text>
+					<text class="price">¥{{order.paymentPrice}}</text>
+				</view>
+			</view>
+		</view>
+		<!-- 订单信息 -->
+		<view class="order-info">
+			<view class="section-header">
+				<view class="header-icon">
+					<uni-icons type="calendar-filled" size="25" color="#a4d5d9"></uni-icons>
+				</view>
+				<text class="header-text">订单信息</text>
+			</view>
+			<view class="info-content">
+				<view class="info-item">
+					<text class="label">订单编号:</text>
+					<text class="value">{{order.orderCode}}</text>
+				</view>
+				<view class="info-item">
+					<text class="label">创建时间:</text>
+					<text class="value">{{order.createTime}}</text>
+				</view>
+				<view class="info-item">
+					<text class="label">支付方式:</text>
+					<text class="value">微信支付</text>
+				</view>
+				<view class="info-item" v-if="order.payTime">
+					<text class="label">支付时间:</text>
+					<text class="value">{{order.payTime}}</text>
+				</view>
+				<view class="info-item" v-if="order.shipTime">
+					<text class="label">发货时间:</text>
+					<text class="value">{{order.shipTime}}</text>
+				</view>
+				<view class="info-item" v-if="order.confirmTime">
+					<text class="label">确认收货:</text>
+					<text class="value">{{order.confirmTime}}</text>
+				</view>
+				<view class="info-item" v-if="order.remark">
+					<text class="label">订单备注:</text>
+					<text class="value">{{order.remark}}</text>
+				</view>
+			</view>
+		</view>
+
+
+
+		<!-- 底部按钮 -->
+		<view class="bottom-btns">
+			<template v-if="order.status === 1">
+				<u-button type="primary" size="large" @click="cancelOrder" color="#999"
+					class="action-btn">取消订单</u-button>
+				<u-button type="primary" size="large" @click="payOrder" color="#D93025"
+					class="action-btn primary">立即支付</u-button>
+			</template>
+
+			<template v-if="order.status === 2">
+				<!-- 待发货状态不显示任何按钮 -->
+			</template>
+
+			<template v-if="order.status === 3">
+				<u-button type="primary" size="large" @click="confirmReceive" color="#D93025"
+					class="action-btn primary">确认收货</u-button>
+			</template>
+
+			<template v-if="order.status === 4">
+				<u-button type="primary" size="large" @click="goToScore" color="#FF9500" class="action-btn"
+					v-if="!order.isEvaluate">评价</u-button>
+				<u-button type="primary" size="large" @click="buyAgain" color="#007AFF"
+					class="action-btn">再次购买</u-button>
+
+			</template>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		orderDetail,
+		confirmReceipt,
+		cancelOrder
+	} from '@/config/api.js';
+	export default {
+		data() {
+			return {
+				orderId: '',
+				order: {
+					id: '',
+					createTime: '',
+					status: 1,
+					orderCode: '',
+					payCode: '',
+					remark: '',
+					paymentPrice: 0,
+					payType: 1,
+					payTime: '',
+					addressId: '',
+					name: '',
+					mobile: '',
+					province: '',
+					city: '',
+					area: '',
+					address: '',
+					expressCompany: '',
+					expressCode: '',
+					shipTime: '',
+					confirmTime: '',
+					isAfter: 0,
+					isEvaluate: 0
+				},
+				orderListVos: []
+			}
+		},
+		onLoad(options) {
+			if (options.id) {
+				this.orderId = options.id
+				this.getOrderDetail()
+			}
+		},
+		methods: {
+			async getOrderDetail() {
+				try {
+					const res = await orderDetail({
+						id: this.orderId
+					})
+					if (res.success && res.data) {
+						this.order = res.data.order
+						this.orderListVos = res.data.orderListVos || []
+					}
+				} catch (e) {
+					this.$u.toast('获取订单详情失败')
+				}
+			},
+
+			// 获取状态文本
+			getStatusText() {
+				const statusMap = {
+					1: '待付款',
+					2: '待发货',
+					3: '待收货',
+					4: '已完成'
+				}
+				return statusMap[this.order.status] || '未知状态'
+			},
+
+			// 获取状态描述
+			getStatusDesc() {
+				const descMap = {
+					1: '请尽快完成支付,超时订单将自动取消',
+					2: '商家正在为您准备商品,请耐心等待',
+					3: '商品正在配送中,请注意查收',
+					4: '订单已完成,感谢您的购买'
+				}
+				return descMap[this.order.status] || ''
+			},
+
+			// 获取状态图片
+			getStatusImage() {
+				const imageMap = {
+					1: '/static/images/order/daifukuan.png',
+					2: '/static/images/order/daifahuo.png',
+					3: '/static/images/order/daishouhuo.png',
+					4: '/static/images/order/yiwancheng.png'
+				}
+				return imageMap[this.order.status] || imageMap[1]
+			},
+
+			// 获取状态样式类
+			getStatusClass() {
+				const classMap = {
+					1: 'status-waiting',
+					2: 'status-processing',
+					3: 'status-shipping',
+					4: 'status-completed'
+				}
+				return classMap[this.order.status] || 'status-waiting'
+			},
+
+
+
+			// 计算商品总价
+			getTotalProductPrice() {
+				try {
+					const total = this.orderListVos.reduce((sum, item) => {
+						const amount = parseFloat(item.subtotalAmount) || 0;
+						return sum + amount;
+					}, 0);
+					return total.toFixed(2);
+				} catch (e) {
+					return '0.00';
+				}
+			},
+
+			async cancelOrder() {
+				try {
+					// 确认对话框
+					const result = await new Promise((resolve) => {
+						uni.showModal({
+							title: '提示',
+							content: '确定要取消这个订单吗?',
+							confirmText: '确定',
+							cancelText: '取消',
+							success: (res) => {
+								resolve(res.confirm);
+							}
+						});
+					});
+
+					if (!result) {
+						return;
+					}
+
+					// 调用取消订单接口
+					const response = await cancelOrder({
+						id: this.orderId
+					});
+
+					if (response.code === 200) {
+						this.$u.toast('取消订单成功');
+						// 重新获取订单详情
+						this.getOrderDetail();
+					} else {
+						this.$u.toast(response.msg || '取消订单失败');
+					}
+				} catch (e) {
+					console.error('取消订单失败:', e);
+					this.$u.toast('取消订单失败');
+				}
+			},
+			payOrder() {
+				uni.navigateTo({
+					url: '/packageOrder/pages/payment/index?id=' + this.orderId
+				})
+			},
+			viewLogistics() {
+				uni.navigateTo({
+					url: '/packageOrder/pages/logistics/index?id=' + this.orderId
+				})
+			},
+			async confirmReceive() {
+				try {
+					let res = await confirmReceipt({
+						id: this.orderId
+					})
+					if (res.code == 200) {
+
+						uni.showToast({
+							title: "确认收货成功",
+							icon: 'success',
+							duration: 1000
+						})
+						setTimeout(() => {
+							this.getOrderDetail()
+						}, 500)
+					}
+
+				} catch (e) {
+					this.$u.toast('确认收货失败')
+				}
+			},
+			goToScore() {
+				uni.navigateTo({
+					url: '/packageOrder/pages/score/index?orderId=' + this.order.id
+				})
+			},
+			buyAgain() {
+				const product = this.orderListVos[0]
+				uni.navigateTo({
+					url: '/packageShop/pages/detail/index?id=' + product.productId
+				})
+			},
+			goDetail(item) {
+				uni.navigateTo({
+					url: '/packageShop/pages/detail/index?id=' + item.productId
+				})
+			},
+			async deleteOrder() {
+				try {
+					await this.$api.order.delete(this.orderId)
+					this.$u.toast('删除订单成功')
+					uni.navigateBack()
+				} catch (e) {
+					this.$u.toast('删除订单失败')
+				}
+			},
+
+			// 复制快递单号
+			copyExpressCode() {
+				uni.setClipboardData({
+					data: this.order.expressCode,
+					success: () => {
+						uni.showToast({
+							title: '复制成功',
+							icon: 'success',
+							duration: 1500
+						})
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.order-detail {
+		min-height: 100vh;
+		background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
+		padding-bottom: 160rpx;
+
+		.status-section {
+			padding: 60rpx 30rpx;
+			position: relative;
+			overflow: hidden;
+
+			.logistics-btn {
+				position: absolute;
+				top: 20rpx;
+				right: 20rpx;
+				z-index: 2;
+
+				.custom-logistics-btn {
+					background: rgba(255, 255, 255, 0.9);
+					border: none;
+					padding: 16rpx 32rpx;
+					border-radius: 40rpx;
+					font-size: 28rpx;
+					display: flex;
+					align-items: center;
+					color: #333;
+					font-weight: 500;
+					box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+					transition: all 0.3s ease;
+
+					.logistics-icon {
+						width: 36rpx;
+						height: 36rpx;
+						margin-right: 8rpx;
+					}
+
+					&:active {
+						transform: scale(0.95);
+						background: rgba(255, 255, 255, 1);
+					}
+				}
+			}
+
+			&::before {
+				content: '';
+				position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
+			}
+
+			&.status-waiting {
+				background: linear-gradient(135deg, #FF6B6B 0%, #D93025 100%);
+			}
+
+			&.status-processing {
+				background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
+			}
+
+			&.status-shipping {
+				background: linear-gradient(135deg, #45B7D1 0%, #96C93D 100%);
+			}
+
+			&.status-completed {
+				background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+			}
+
+			.status-bg-pattern {
+				position: absolute;
+				top: 0;
+				left: 0;
+				width: 100%;
+				height: 100%;
+				background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAwIiBoZWlnaHQ9IjEwMDAiPgo8cmVjdCB3aWR0aD0iMTAwMCIgaGVpZ2h0PSIxMDAwIiBmaWxsPSIjZmZmIi8+Cjwvc3ZnPgo=');
+				background-size: 200% 200%;
+				opacity: 0.1;
+				z-index: -1;
+			}
+
+			.status-content {
+				position: relative;
+				z-index: 1;
+				display: flex;
+				align-items: center;
+				color: #fff;
+
+				.status-icon {
+					margin-right: 30rpx;
+					width: 120rpx;
+					height: 120rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+
+					.status-img {
+						width: 100%;
+						height: 100%;
+					}
+				}
+
+				.status-info {
+					flex: 1;
+
+					.status-text {
+						font-size: 40rpx;
+						font-weight: bold;
+						margin-bottom: 10rpx;
+					}
+
+					.status-desc {
+						font-size: 26rpx;
+						opacity: 0.9;
+						line-height: 1.4;
+					}
+				}
+			}
+		}
+
+		.section-header {
+			display: flex;
+			align-items: center;
+			margin-bottom: 20rpx;
+
+			.header-icon {
+				margin-right: 15rpx;
+			}
+
+			.header-text {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #333;
+				margin-left: 15rpx;
+			}
+		}
+
+		.address-section {
+			background: #fff;
+			padding: 30rpx;
+			margin: 20rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+			border: 1rpx solid rgba(217, 48, 37, 0.1);
+
+			.address-content {
+				.user-info {
+					margin-bottom: 15rpx;
+
+					.name {
+						font-size: 32rpx;
+						font-weight: bold;
+						color: #333;
+						margin-right: 20rpx;
+					}
+
+					.phone {
+						font-size: 28rpx;
+						color: #666;
+					}
+				}
+
+				.address {
+					font-size: 28rpx;
+					color: #333;
+					line-height: 1.5;
+					margin-bottom: 20rpx;
+				}
+
+				.logistics-info {
+					.divider {
+						height: 1rpx;
+						background: #f0f0f0;
+						margin: 20rpx 0;
+					}
+
+					.logistics-item {
+						display: flex;
+						align-items: center;
+						margin-bottom: 16rpx;
+
+						&:last-child {
+							margin-bottom: 0;
+						}
+
+						.label {
+							width: 140rpx;
+							font-size: 26rpx;
+							color: #666;
+						}
+
+						.value {
+							flex: 1;
+							font-size: 26rpx;
+							color: #333;
+						}
+
+						.value-copy {
+							flex: 1;
+							display: flex;
+							align-items: center;
+							justify-content: space-between;
+
+							.value {
+								flex: 1;
+								margin-right: 20rpx;
+							}
+
+							.copy-btn {
+								font-size: 24rpx;
+								color: #007AFF;
+								background: rgba(0, 122, 255, 0.1);
+								padding: 6rpx 20rpx;
+								border-radius: 30rpx;
+								border: none;
+								line-height: 1.5;
+
+								&:active {
+									background: rgba(0, 122, 255, 0.2);
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+
+		.product-section {
+			background: #fff;
+			padding: 30rpx;
+			margin: 0 20rpx 20rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+			border: 1rpx solid rgba(0, 0, 0, 0.05);
+
+			.product-list {
+				.product-item {
+					display: flex;
+					padding: 25rpx 0;
+					border-bottom: 1rpx solid #f0f0f0;
+
+					&:last-child {
+						border-bottom: none;
+					}
+
+					.product-image-wrapper {
+						width: 180rpx;
+						height: 180rpx;
+						border-radius: 16rpx;
+						overflow: hidden;
+						margin-right: 25rpx;
+						box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.15);
+						position: relative;
+
+						&::after {
+							content: '';
+							position: absolute;
+							top: 0;
+							left: 0;
+							right: 0;
+							bottom: 0;
+							background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
+							pointer-events: none;
+						}
+					}
+
+					.product-image {
+						width: 100%;
+						height: 100%;
+					}
+
+					.product-info {
+						flex: 1;
+						display: flex;
+						flex-direction: column;
+						justify-content: space-between;
+
+						.product-name {
+							font-size: 30rpx;
+							color: #333;
+							font-weight: 500;
+							line-height: 1.4;
+							margin-bottom: 10rpx;
+						}
+
+						.product-spec {
+							font-size: 24rpx;
+							color: #666;
+							background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+							padding: 6rpx 12rpx;
+							border-radius: 12rpx;
+							border: 1rpx solid rgba(0, 0, 0, 0.05);
+						}
+
+						.product-price-count {
+							display: flex;
+							justify-content: space-between;
+							align-items: flex-end;
+
+							.price-info {
+								display: flex;
+								flex-direction: column;
+								align-items: flex-start;
+								flex: 1;
+							}
+
+							.price {
+								font-size: 34rpx;
+								color: #D93025;
+								font-weight: bold;
+								margin-bottom: 8rpx;
+							}
+
+							.subtotal {
+								font-size: 24rpx;
+								color: #666;
+								background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+								padding: 4rpx 8rpx;
+								border-radius: 8rpx;
+							}
+
+							.count {
+								font-size: 26rpx;
+								color: #666;
+								background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+								padding: 8rpx 16rpx;
+								border-radius: 16rpx;
+								white-space: nowrap;
+								border: 1rpx solid rgba(0, 0, 0, 0.05);
+								font-weight: 500;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		.logistics-section {
+			background: #fff;
+			padding: 30rpx;
+			margin: 0 20rpx 20rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+			border: 1rpx solid rgba(0, 122, 255, 0.1);
+
+			.logistics-content {
+				.logistics-item {
+					display: flex;
+					margin-bottom: 15rpx;
+
+					&:last-child {
+						margin-bottom: 0;
+					}
+
+					.label {
+						width: 160rpx;
+						font-size: 28rpx;
+						color: #666;
+					}
+
+					.value {
+						flex: 1;
+						font-size: 28rpx;
+						color: #333;
+					}
+				}
+			}
+		}
+
+		.order-info {
+			background: #fff;
+			padding: 30rpx;
+			margin: 0 20rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+			border: 1rpx solid rgba(0, 0, 0, 0.05);
+
+			.info-content {
+				.info-item {
+					display: flex;
+					margin-bottom: 20rpx;
+
+					&:last-child {
+						margin-bottom: 0;
+					}
+
+					.label {
+						width: 160rpx;
+						font-size: 28rpx;
+						color: #666;
+					}
+
+					.value {
+						flex: 1;
+						font-size: 28rpx;
+						color: #646464;
+					}
+				}
+			}
+		}
+
+		.amount-section {
+			background: #fff;
+			padding: 30rpx;
+			margin: 0 20rpx 20rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
+			border: 1rpx solid rgba(217, 48, 37, 0.1);
+
+			.amount-content {
+				.amount-item {
+					display: flex;
+					justify-content: space-between;
+					margin-bottom: 20rpx;
+					font-size: 28rpx;
+					color: #666;
+
+					&.total {
+						margin-top: 30rpx;
+						padding-top: 30rpx;
+						border-top: 1rpx solid #f0f0f0;
+						color: #333;
+						font-weight: bold;
+						font-size: 32rpx;
+					}
+
+					.price {
+						color: #D93025;
+						font-weight: bold;
+					}
+				}
+			}
+		}
+
+		.bottom-btns {
+			position: fixed;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			background: #fff;
+			padding: 30rpx;
+			display: flex;
+			justify-content: space-between;
+			gap: 20rpx;
+			box-shadow: 0 -8rpx 30rpx rgba(0, 0, 0, 0.12);
+			border-top: 1rpx solid #f0f0f0;
+
+			.action-btn {
+				flex: 1;
+				border-radius: 50rpx;
+				font-weight: 600;
+				font-size: 32rpx;
+				height: 88rpx;
+				line-height: 88rpx;
+				box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
+				transition: all 0.3s ease;
+
+				&:active {
+					transform: translateY(2rpx);
+					box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
+				}
+			}
+
+			.primary {
+				background: linear-gradient(135deg, #D93025 0%, #FF6B6B 100%);
+				color: #fff;
+				box-shadow: 0 4rpx 15rpx rgba(217, 48, 37, 0.3);
+			}
+		}
+	}
+</style>

+ 474 - 0
packageOrder/pages/list/index.vue

@@ -0,0 +1,474 @@
+<template>
+	<view class="bg">
+		<u-sticky>
+			<view class="pad-t-10 ">
+				<view class="flex-items bg-white pad-16" @tap="goSearch">
+					<uni-icons type="search" color="#a8a8a8" size="28"></uni-icons>
+
+					<view v-if="params.orderCode" class="font28">
+						{{params.orderCode}}
+					</view>
+					<view class="font-gray font28" v-else>
+						请输入订单号或商品名称
+					</view>
+				</view>
+			</view>
+			<view class="bg-white">
+				<u-tabs :current="current" lineWidth="30" :list="tabList" @click="tabClick"></u-tabs>
+			</view>
+		</u-sticky>
+		<view v-if="orderList.length>0">
+			<view v-for="(item,index) in orderList" :key="index">
+				<view class="info-region">
+					<view @tap="goDetail(item)">
+						<view class="flex-items flex-sp-between mar-b-20 mar-lr-12">
+							<view>
+								订单号:{{item.orderCode}}
+							</view>
+							<view v-if="item.isAfter==0||item.isAfter==3">
+								<text class="fukuan" v-if="item.status==1">
+									待付款
+								</text>
+								<text class="daifahuo" v-if="item.status==2">
+									待发货
+								</text>
+								<text class="shouhuo" v-if="item.status==3">
+									待收货
+								</text>
+								<text class="pingjia mar-r-10" v-if="item.status==4&&item.isEvaluate==0">
+									待评价
+								</text>
+								<text class="wancheng" v-if="item.status==4">
+									已完成
+								</text>
+							</view>
+
+
+						</view>
+						<view class="flex">
+							<view>
+								<image class=" mar-l-20 img" :src="item.images" mode="aspectFit"></image>
+							</view>
+							<view class="mar-l-20 mar-r-20">
+								<view class="font32 pad-b-10 ">
+									{{item.productName}}
+								</view>
+								<view class="pad-t-10 font-gray font26"
+									v-if="item.productCount && item.productCount > 1">
+									共{{item.productCount}}件商品
+								</view>
+								<view class="pad-t-20 font-gray">
+									规格: {{item.skuName}}
+								</view>
+							</view>
+						</view>
+						<text class="flex-end font-red font28 font-bold mar-r-10">
+							实付款 ¥{{item.paymentPrice}}
+						</text>
+					</view>
+					<view>
+						<view v-if="item.isAfter===0||item.isAfter===0">
+							<!-- 待付款状态 -->
+							<view v-if="item.status === 1" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="payOrder(item)">
+										去付款
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="cancelOrder(item)">
+										取消订单
+									</view>
+								</view>
+							</view>
+							<!-- 待发货状态 -->
+							<view v-if="item.status === 2" class="flex-items flex-end">
+
+								<view class=" mar-t-20 mar-r-10" v-if="item.status === 2">
+									<view class="buyBtn" @click="onceOrder(item)">
+										再来一单
+									</view>
+								</view>
+							</view>
+
+							<!-- 待收货状态 -->
+							<view v-if="item.status === 3" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="logistics(item)">
+										查看物流
+									</view>
+								</view>
+
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="confirmReceiptDialog(item)">
+										确认收货
+									</view>
+								</view>
+							</view>
+
+							<!-- 已完成状态 -->
+							<view v-if="item.status === 4" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="goScore(item)" v-if="item.isEvaluate==0">
+										评价
+									</view>
+								</view>
+
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="onceOrder(item)">
+										再来一单
+									</view>
+								</view>
+							</view>
+
+						</view>
+
+						<!-- 售后中状态 -->
+						<view class=" flex-items flex-end mar-t-20 mar-r-10" v-if="item.isAfter === 1">
+							<view class="cancelBtn" @click="checkAfterSaleStatus(item)">
+								售后进度
+							</view>
+						</view>
+						<!-- 售后完成状态 -->
+						<view class=" flex-items flex-end mar-t-20 mar-r-10" v-if="item.isAfter === 2">
+							<view class="cancelBtn" @click="checkAfterSaleStatus(item)">
+								售后详情
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else>
+			<view class="flex-items-plus">
+				<image src="@/static/images/empty.png" class="empty "></image>
+			</view>
+			<view class="font28 font-gray flex-items-plus">
+				数据为空
+			</view>
+		</view>
+		<u-toast ref="uToast"></u-toast>
+		<uni-popup ref="dialog" type="dialog">
+			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确认" title="提示" content="确认收货?"
+				@confirm="confirmReceipt()"></uni-popup-dialog>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+	import CustomToast from '@/uni_modules/custom-components/components/custom-toast/custom-toast.vue';
+	import {
+		getOrderList,
+		confirmReceipt,
+		cancelOrder
+	} from '@/config/api.js';
+
+	export default {
+		components: {
+			CustomToast
+		},
+		data() {
+			return {
+				current: 0,
+				searchKey: "",
+				tabList: [{
+						name: '全部'
+					},
+					{
+						name: '待付款'
+					},
+					{
+						name: '待发货'
+					},
+					{
+						name: '待收货'
+					},
+					{
+						name: '已完成'
+					},
+
+					{
+						name: '待评价'
+					}
+				],
+				orderList: [],
+				isFirstLoad: true,
+				params: {
+					current: 1,
+					size: 10,
+					status: "",
+					isAfter: "",
+					isEvaluate: "",
+					type: "",
+					orderCode: ""
+				},
+				form: {},
+			};
+		},
+		onLoad(op) {
+			if (op.serachKey) {
+				this.params.orderCode = op.serachKey;
+			}
+			console.log(op);
+			if (op.status && op.status != 0) {
+				this.params.status = op.status;
+				this.current = op.status;
+			}
+			if (op.isAfter) {
+				this.params.isAfter = op.isAfter;
+				this.current = 5;
+			}
+			if (op.isEvaluate) {
+				this.params.isEvaluate = op.isEvaluate;
+				this.current = 6;
+			}
+			this.getList();
+			setTimeout(() => {
+				this.isFirstLoad = false;
+			}, 1000)
+		},
+		onPullDownRefresh() {
+			this.reset()
+			uni.stopPullDownRefresh();
+		},
+		onShow() {
+			console.log(this.isFirstLoad, "11")
+			if (!this.isFirstLoad) {
+
+				this.getList();
+			}
+		},
+		methods: {
+
+			goScore(item) {
+				this.isFirstLoad = false
+				this.$route('/packageOrder/pages/score/index?orderId=' + item.id)
+			},
+			reset() {
+				this.orderList = [];
+				this.params.current = 1;
+				this.getList();
+			},
+			after(item) {
+				this.$route('/packageOrder/pages/after-sale/index?id=' + item.id);
+			},
+			logistics(item) {
+				this.$route('/packageOrder/pages/logistics/index?expressCode=' + item.expressCode);
+			},
+			onceOrder(item) {
+				this.$route('/packageShop/pages/detail/index?skuId=' + item.skuId + '&id=' + item.productId);
+			},
+			goSearch() {
+				this.$route('/packageShop/pages/search/index?type=2');
+			},
+			goDetail(item) {
+				this.$route('/packageOrder/pages/detail/index?id=' + item.id);
+			},
+			tabClick(item) {
+				console.log(item);
+				if (item.index == 5) {
+					this.params.isEvaluate = 0;
+					this.params.isAfter = ""
+					this.params.status = ""
+				} else {
+					this.params.status = item.index;
+					this.params.isAfter = ""
+					this.params.isEvaluate = ""
+				}
+				this.getList();
+			},
+			getList() {
+				getOrderList(this.params).then((res) => {
+					this.orderList = res.data.records;
+				});
+			},
+			payOrder(item) {
+				this.$route('/packageOrder/pages/payment/index?orderId=' + item.id);
+			},
+			async cancelOrder(item) {
+				try {
+					// 确认对话框
+					const result = await new Promise((resolve) => {
+						uni.showModal({
+							title: '提示',
+							content: '确定要取消订单:' + item.orderCode + ' 吗?',
+							confirmText: '确定',
+							cancelText: '取消',
+							success: (res) => {
+								resolve(res.confirm);
+							}
+						});
+					});
+
+					if (!result) {
+						return;
+					}
+
+					// 调用取消订单接口
+					const response = await cancelOrder({
+						id: item.id
+					});
+
+					if (response.code === 200) {
+						this.showToast('取消订单成功');
+						// 重新获取订单列表
+						this.reset();
+					} else {
+						this.showToast(response.msg || '取消订单失败');
+					}
+				} catch (e) {
+					console.error('取消订单失败:', e);
+					this.showToast('取消订单失败');
+				}
+			},
+			confirmReceiptDialog(item) {
+				this.$refs.dialog.open()
+				this.form = item
+			},
+			showToast(text) {
+				this.$refs.uToast.show({
+					type: 'success',
+					message: text
+				})
+			},
+			confirmReceipt() {
+				confirmReceipt(this.form).then((res) => {
+					if (res.code == 200) {
+						this.$refs.dialog.close()
+						this.showToast('操作成功')
+						this.reset()
+					}
+				})
+			},
+			checkAfterSaleStatus(item) {
+				this.$route('/packageOrder/pages/after-sale/index?orderId=' + item.id);
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.bg {
+		min-height: 100vh;
+		padding-bottom: 20rpx;
+	}
+
+	.info-region {
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		border-radius: 15rpx;
+		width: 720rpx;
+		height: auto;
+		padding: 15rpx;
+		background-color: white;
+		margin-top: 10rpx;
+		margin-bottom: 20rpx;
+		margin-left: 15rpx;
+	}
+
+	.img {
+		border-radius: 20rpx;
+		width: 200rpx;
+		height: 200rpx;
+	}
+
+	.daifahuo {
+		color: #ffffff;
+		background-color: #e2c696;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.fukuan {
+		color: #ffffff;
+		background-color: #e29c6d;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhuo {
+		color: #ffffff;
+		background-color: #e2d972;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.wancheng {
+		color: #ffffff;
+		background-color: #83e25a;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.pingjia {
+		color: #ffffff;
+		background-color: #e0e2d9;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhou {
+		color: #ffffff;
+		background-color: #e23e3e;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.buyBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #fff;
+		background: #E2A28A;
+	}
+
+	.cancelBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #666666;
+		background: #f5f5f5;
+	}
+
+	.empty {
+		width: 400rpx;
+		height: 400rpx;
+		margin-top: 200rpx;
+	}
+
+	.nav-back {
+		position: fixed;
+		left: 30rpx;
+		top: calc(var(--status-bar-height) + 20rpx);
+		z-index: 100;
+
+		.back-btn {
+			width: 80rpx;
+			height: 80rpx;
+			background: rgba(255, 255, 255, 0.8);
+			border: 1rpx solid rgba(0, 0, 0, 0.1);
+			border-radius: 50%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			transition: all 0.3s ease;
+			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+			&:active {
+				transform: scale(0.95);
+				box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
+			}
+		}
+
+		.back-icon {
+			font-family: "iconfont";
+			font-size: 36rpx;
+			color: #333;
+		}
+	}
+</style>

+ 692 - 0
packageOrder/pages/list/record.vue

@@ -0,0 +1,692 @@
+<template>
+	<view class="container">
+		<!-- 用户信息 -->
+		<view class="user-info">
+			<image class="user-avatar" :src="statistics.avatar || '/static/images/avatar.png'" mode="aspectFill"></image>
+			<view class="user-name">{{ statistics.nickName || '用户' }}的订单记录</view>
+		</view>
+
+		<!-- 时间筛选 -->
+		<view class="time-filter">
+			<view class="filter-tabs">
+				<view class="tab-item" :class="{ active: currentTimeFilter === index }"
+					v-for="(item, index) in timeFilterOptions" :key="index" @tap="selectTimeFilter(index)">
+					{{ item.name }}
+				</view>
+			</view>
+
+			<!-- 自定义时间范围 -->
+			<view v-if="currentTimeFilter === 2" class="custom-time">
+				<view class="time-row">
+					<text class="label">开始时间:</text>
+					<picker mode="date" :value="customStartTime" @change="onStartTimeChange">
+						<view class="time-picker">{{ customStartTime || '请选择' }}</view>
+					</picker>
+				</view>
+				<view class="time-row">
+					<text class="label">结束时间:</text>
+					<picker mode="date" :value="customEndTime" @change="onEndTimeChange">
+						<view class="time-picker">{{ customEndTime || '请选择' }}</view>
+					</picker>
+				</view>
+			</view>
+		</view>
+
+		<!-- 统计卡片 -->
+		<view class="statistics-cards">
+			<view class="card-row">
+				<view class="stat-card">
+					<view class="card-value">{{ statistics.totalOrderCount || 0 }}</view>
+					<view class="card-label">总订单数</view>
+				</view>
+				<view class="stat-card">
+					<view class="card-value">{{ statistics.totalProductCount || 0 }}</view>
+					<view class="card-label">总商品数</view>
+				</view>
+			</view>
+
+			<view class="card-row">
+				<view class="stat-card">
+					<view class="card-value">¥{{ statistics.totalAmount || '0.00' }}</view>
+					<view class="card-label">总金额</view>
+				</view>
+				<view class="stat-card">
+					<view class="card-value">¥{{ statistics.paidAmount || '0.00' }}</view>
+					<view class="card-label">已付金额</view>
+				</view>
+			</view>
+
+			<view class="card-row">
+				<view class="stat-card">
+					<view class="card-value">¥{{ statistics.averageOrderAmount || '0.00' }}</view>
+					<view class="card-label">平均订单金额</view>
+				</view>
+				<view class="stat-card">
+					<view class="card-value">
+						{{ statistics.latestOrderTime ? formatTime(statistics.latestOrderTime) : '无' }}</view>
+					<view class="card-label">最近下单时间</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单状态统计 -->
+		<view class="status-statistics">
+			<view class="section-title">订单状态分布</view>
+			<view class="status-grid">
+				<view class="status-item">
+					<view class="status-count">{{ statistics.pendingPaymentCount || 0 }}</view>
+					<view class="status-label">待付款</view>
+				</view>
+				<view class="status-item">
+					<view class="status-count">{{ statistics.pendingShipmentCount || 0 }}</view>
+					<view class="status-label">待发货</view>
+				</view>
+				<view class="status-item">
+					<view class="status-count">{{ statistics.pendingReceiptCount || 0 }}</view>
+					<view class="status-label">待收货</view>
+				</view>
+				<view class="status-item">
+					<view class="status-count">{{ statistics.completedCount || 0 }}</view>
+					<view class="status-label">已完成</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单列表 -->
+		<view class="order-section">
+			<view class="section-title">订单记录</view>
+			<view v-if="orderList.length > 0" class="order-list">
+				<view v-for="(item, index) in orderList" :key="index" class="order-item" @tap="goOrderDetail(item)">
+					<view class="order-header">
+						<view class="order-code">订单号: {{ item.orderCode }}</view>
+						<view class="order-status" :class="getStatusClass(item.status, item.isAfter)">
+							{{ getStatusText(item.status, item.isAfter, item.isEvaluate) }}
+						</view>
+					</view>
+					<view class="order-content">
+						<image class="product-image" :src="item.images" mode="aspectFit"></image>
+						<view class="product-info">
+							<view class="product-name">{{ item.productName }}</view>
+							<view class="product-spec">规格: {{ item.skuName }}</view>
+							<view class="product-count">数量: {{ item.count }}</view>
+						</view>
+						<view class="order-price">
+							<view class="price">¥{{ item.paymentPrice }}</view>
+						</view>
+					</view>
+					<view class="order-time">下单时间: {{ formatTime(item.createTime) }}</view>
+				</view>
+			</view>
+			<view v-else class="empty-state">
+				<image src="@/static/images/empty.png" class="empty-image"></image>
+				<view class="empty-text">暂无订单记录</view>
+			</view>
+		</view>
+
+		<!-- 加载更多 -->
+		<view v-if="orderList.length > 0 && hasMore" class="load-more" @tap="loadMore">
+			<view class="load-text">加载更多</view>
+		</view>
+
+		<view v-if="loading" class="loading">
+			<uni-load-more status="loading"></uni-load-more>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getMemberOrderStatistics,
+		getMemberOrderList
+	} from '@/config/api.js';
+
+	export default {
+		data() {
+			return {
+				memberId: '', // 用户ID
+				currentTimeFilter: 1, // 当前选中的时间筛选(0: 上个月, 1: 这个月, 2: 自定义)
+				timeFilterOptions: [{
+						name: '上个月'
+					},
+					{
+						name: '这个月'
+					},
+					{
+						name: '自定义'
+					}
+				],
+				customStartTime: '',
+				customEndTime: '',
+				statistics: {}, // 统计数据
+				orderList: [], // 订单列表
+				loading: false,
+				hasMore: true,
+				params: {
+					current: 1,
+					size: 10,
+					status: "",
+					isAfter: "",
+					isEvaluate: "",
+					orderCode: ""
+				}
+			}
+		},
+		onLoad(options) {
+			if (options.memberId) {
+				this.memberId = options.memberId;
+				this.loadStatistics();
+				this.loadOrderList();
+			}
+		},
+		onPullDownRefresh() {
+			this.refreshData();
+			uni.stopPullDownRefresh();
+		},
+		onReachBottom() {
+			if (this.hasMore && !this.loading) {
+				this.loadMore();
+			}
+		},
+		methods: {
+			goBack() {
+				uni.navigateBack();
+			},
+
+			// 选择时间筛选
+			selectTimeFilter(index) {
+				this.currentTimeFilter = index;
+				if (index !== 2) {
+					this.customStartTime = '';
+					this.customEndTime = '';
+				}
+				this.loadStatistics();
+				this.refreshOrderList();
+			},
+
+			// 开始时间选择
+			onStartTimeChange(e) {
+				this.customStartTime = e.detail.value;
+				if (this.customEndTime) {
+					this.loadStatistics();
+					this.refreshOrderList();
+				}
+			},
+
+			// 结束时间选择
+			onEndTimeChange(e) {
+				this.customEndTime = e.detail.value;
+				if (this.customStartTime) {
+					this.loadStatistics();
+					this.refreshOrderList();
+				}
+			},
+
+			// 获取时间范围
+			getTimeRange() {
+				const now = new Date();
+				let startTime = '';
+				let endTime = '';
+
+				if (this.currentTimeFilter === 0) {
+					// 上个月
+					const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+					const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0);
+					startTime = this.formatDate(lastMonth);
+					endTime = this.formatDate(lastMonthEnd);
+				} else if (this.currentTimeFilter === 1) {
+					// 这个月
+					const thisMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
+					startTime = this.formatDate(thisMonthStart);
+					endTime = this.formatDate(now);
+				} else if (this.currentTimeFilter === 2) {
+					// 自定义
+					startTime = this.customStartTime;
+					endTime = this.customEndTime;
+				}
+
+				return {
+					startTime,
+					endTime
+				};
+			},
+
+			// 加载统计数据
+			async loadStatistics() {
+				if (!this.memberId) return;
+
+				const {
+					startTime,
+					endTime
+				} = this.getTimeRange();
+				const params = {
+					memberId: this.memberId,
+					startTime,
+					endTime
+				};
+
+				try {
+					const res = await getMemberOrderStatistics(params);
+					if (res.code === 200) {
+						this.statistics = res.data || {};
+					}
+				} catch (error) {
+					console.error('加载统计数据失败:', error);
+				}
+			},
+
+			// 刷新订单列表
+			refreshOrderList() {
+				this.params.current = 1;
+				this.orderList = [];
+				this.hasMore = true;
+				this.loadOrderList();
+			},
+
+			// 加载订单列表
+			async loadOrderList() {
+				if (this.loading || !this.hasMore) return;
+
+				this.loading = true;
+
+				try {
+					// 获取时间范围
+					const { startTime, endTime } = this.getTimeRange();
+					
+					// 设置用户ID查询条件和时间筛选
+					const orderParams = {
+						...this.params,
+						memberId: this.memberId,
+						startTime: startTime,
+						endTime: endTime
+					};
+
+					const res = await getMemberOrderList(orderParams);
+					if (res.code === 200) {
+						const newOrders = res.data.records || [];
+						if (this.params.current === 1) {
+							this.orderList = newOrders;
+						} else {
+							this.orderList = [...this.orderList, ...newOrders];
+						}
+
+						this.hasMore = newOrders.length >= this.params.size;
+					}
+				} catch (error) {
+					console.error('加载订单列表失败:', error);
+					uni.showToast({
+						title: '加载失败',
+						icon: 'none'
+					});
+				} finally {
+					this.loading = false;
+				}
+			},
+
+			// 加载更多
+			loadMore() {
+				this.params.current++;
+				this.loadOrderList();
+			},
+
+			// 刷新数据
+			refreshData() {
+				this.loadStatistics();
+				this.refreshOrderList();
+			},
+
+			// 跳转到订单详情
+			goOrderDetail(item) {
+				uni.navigateTo({
+					url: `/packageOrder/pages/detail/index?id=${item.id}`
+				});
+			},
+
+			// 获取订单状态样式
+			getStatusClass(status, isAfter) {
+				if (isAfter > 0) return 'status-after';
+				if (status === 1) return 'status-pending';
+				if (status === 2) return 'status-paid';
+				if (status === 3) return 'status-shipped';
+				if (status === 4) return 'status-completed';
+				return '';
+			},
+
+			// 获取订单状态文本
+			getStatusText(status, isAfter, isEvaluate) {
+				if (isAfter === 1) return '售后中';
+				if (isAfter === 2) return '售后完成';
+				if (isAfter === 3) return '售后拒绝';
+				if (status === 1) return '待付款';
+				if (status === 2) return '待发货';
+				if (status === 3) return '待收货';
+				if (status === 4) {
+					if (isEvaluate === 0) return '待评价';
+					return '已完成';
+				}
+				return '未知状态';
+			},
+
+			// 格式化日期
+			formatDate(date) {
+				const year = date.getFullYear();
+				const month = String(date.getMonth() + 1).padStart(2, '0');
+				const day = String(date.getDate()).padStart(2, '0');
+				return `${year}-${month}-${day}`;
+			},
+
+			// 格式化时间
+			formatTime(timeStr) {
+				if (!timeStr) return '';
+				const date = new Date(timeStr);
+				return `${date.getMonth() + 1}-${date.getDate()} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding-bottom: 40rpx;
+	}
+
+	// 用户信息
+	.user-info {
+		background: #fff;
+		padding: 40rpx 30rpx;
+		display: flex;
+		align-items: center;
+		margin-bottom: 20rpx;
+
+		.user-avatar {
+			width: 100rpx;
+			height: 100rpx;
+			border-radius: 50%;
+			margin-right: 20rpx;
+		}
+
+		.user-name {
+			font-size: 34rpx;
+			font-weight: 600;
+			color: #333;
+		}
+	}
+
+	// 时间筛选
+	.time-filter {
+		margin-top: 0;
+		background: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+
+		.filter-tabs {
+			display: flex;
+			border-radius: 10rpx;
+			overflow: hidden;
+			border: 2rpx solid #7C9DC3;
+
+			.tab-item {
+				flex: 1;
+				text-align: center;
+				padding: 20rpx 0;
+				font-size: 28rpx;
+				background: #fff;
+				color: #7C9DC3;
+				transition: all 0.3s;
+
+				&.active {
+					background: #7C9DC3;
+					color: #fff;
+				}
+			}
+		}
+
+		.custom-time {
+			margin-top: 30rpx;
+
+			.time-row {
+				display: flex;
+				align-items: center;
+				margin-bottom: 20rpx;
+
+				.label {
+					width: 160rpx;
+					font-size: 28rpx;
+					color: #666;
+				}
+
+				.time-picker {
+					flex: 1;
+					padding: 20rpx;
+					background: #f8f8f8;
+					border-radius: 8rpx;
+					font-size: 28rpx;
+					color: #333;
+				}
+			}
+		}
+	}
+
+	// 统计卡片
+	.statistics-cards {
+		background: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+
+		.card-row {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 30rpx;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			.stat-card {
+				flex: 1;
+				text-align: center;
+				padding: 30rpx 20rpx;
+				background: linear-gradient(135deg, #7C9DC3 0%, #9DBAD9 100%);
+				border-radius: 12rpx;
+				margin: 0 10rpx;
+
+				&:first-child {
+					margin-left: 0;
+				}
+
+				&:last-child {
+					margin-right: 0;
+				}
+
+				.card-value {
+					font-size: 40rpx;
+					font-weight: bold;
+					color: #fff;
+					margin-bottom: 10rpx;
+				}
+
+				.card-label {
+					font-size: 24rpx;
+					color: rgba(255, 255, 255, 0.9);
+				}
+			}
+		}
+	}
+
+	// 订单状态统计
+	.status-statistics {
+		background: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+
+		.section-title {
+			font-size: 32rpx;
+			font-weight: 600;
+			color: #333;
+			margin-bottom: 30rpx;
+		}
+
+		.status-grid {
+			display: flex;
+			justify-content: space-between;
+
+			.status-item {
+				flex: 1;
+				text-align: center;
+				padding: 20rpx 10rpx;
+
+				.status-count {
+					font-size: 36rpx;
+					font-weight: bold;
+					color: #7C9DC3;
+					margin-bottom: 8rpx;
+				}
+
+				.status-label {
+					font-size: 24rpx;
+					color: #666;
+				}
+			}
+		}
+	}
+
+	// 订单列表
+	.order-section {
+		background: #fff;
+		padding: 30rpx;
+
+		.section-title {
+			font-size: 32rpx;
+			font-weight: 600;
+			color: #333;
+			margin-bottom: 30rpx;
+		}
+
+		.order-list {
+			.order-item {
+				border: 2rpx solid #f0f0f0;
+				border-radius: 12rpx;
+				padding: 30rpx;
+				margin-bottom: 20rpx;
+				background: #fff;
+
+				.order-header {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					margin-bottom: 20rpx;
+
+					.order-code {
+						font-size: 28rpx;
+						color: #666;
+					}
+
+					.order-status {
+						padding: 8rpx 16rpx;
+						border-radius: 20rpx;
+						font-size: 24rpx;
+						color: #fff;
+
+						&.status-pending {
+							background: #FFB98B;
+						}
+
+						&.status-paid {
+							background: #7C9DC3;
+						}
+
+						&.status-shipped {
+							background: #8BC4D9;
+						}
+
+						&.status-completed {
+							background: #92C3A9;
+						}
+
+						&.status-after {
+							background: #D99595;
+						}
+					}
+				}
+
+				.order-content {
+					display: flex;
+					align-items: center;
+					margin-bottom: 15rpx;
+
+					.product-image {
+						width: 120rpx;
+						height: 120rpx;
+						border-radius: 8rpx;
+						margin-right: 20rpx;
+					}
+
+					.product-info {
+						flex: 1;
+
+						.product-name {
+							font-size: 30rpx;
+							color: #333;
+							margin-bottom: 10rpx;
+							font-weight: 500;
+						}
+
+						.product-spec,
+						.product-count {
+							font-size: 24rpx;
+							color: #666;
+							margin-bottom: 5rpx;
+						}
+					}
+
+					.order-price {
+						.price {
+							font-size: 32rpx;
+							font-weight: bold;
+							color: #7C9DC3;
+						}
+					}
+				}
+
+				.order-time {
+					font-size: 24rpx;
+					color: #999;
+					text-align: right;
+				}
+			}
+		}
+
+		.empty-state {
+			text-align: center;
+			padding: 100rpx 0;
+
+			.empty-image {
+				width: 300rpx;
+				height: 300rpx;
+				margin-bottom: 30rpx;
+			}
+
+			.empty-text {
+				font-size: 28rpx;
+				color: #999;
+			}
+		}
+	}
+
+	// 加载更多
+	.load-more {
+		text-align: center;
+		padding: 30rpx;
+
+		.load-text {
+			font-size: 28rpx;
+			color: #666;
+		}
+	}
+
+	.loading {
+		text-align: center;
+		padding: 30rpx;
+	}
+</style>

+ 377 - 0
packageOrder/pages/logistics/index.vue

@@ -0,0 +1,377 @@
+<template>
+	<view class="">
+		<!-- 物流头部信息 -->
+		<view class="logistics-header">
+			<view>
+				<view class="company-info mar-b-16">
+					<u-icon name="car" size="25" color="#8b8b8b" class="logistics-header-icon"></u-icon>
+					<text class="label">物流公司:</text>
+					<text class="value">{{ order.expressCompany  ||'-'}}</text>
+				</view>
+				<view class="tracking-info mar-b-16">
+					<u-icon name="order" size="25" color="#8b8b8b" class="logistics-header-icon"></u-icon>
+					<text class="label">单号:</text>
+					<text class="value">{{ order.expressCode  ||'-'}}</text>
+					<text class="copy-btn" v-if="order.expressCode" @tap="copyContent">复制</text>
+				</view>
+				<view class="company-info mar-b-16 ">
+					<u-icon name="coupon" size="25" color="#8b8b8b" class="logistics-header-icon"></u-icon>
+
+					<text class="label">发货备注:</text>
+					<text class="value">{{ order.deliveryRemark ||'-'}}</text>
+				</view>
+			</view>
+		</view>
+
+
+		<!-- 物流轨迹列表 -->
+		<view class="logistics-trace" :key="stepsKey">
+			<template v-if="logisticsList && logisticsList.length">
+				<uni-steps :options="logisticsList" direction="column" :active="0"></uni-steps>
+			</template>
+			<template v-else>
+				<view class="no-data">暂无物流信息</view>
+			</template>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import {
+		queryExpress,
+		orderDetail
+	} from '@/config/api.js'
+
+	export default {
+		data() {
+			return {
+				stepsKey: 0,
+				logisticsCompany: '',
+				logisticsNumber: '',
+				logisticsList: [],
+				order: {},
+				stats: null,
+				orderId: "",
+			};
+		},
+		methods: {
+			copyContent() {
+				uni.setClipboardData({
+					data: this.logisticsNumber,
+					success: () => {
+						uni.showToast({
+							title: '复制成功',
+							icon: 'none'
+						});
+					}
+				});
+			},
+			async getLogisticsInfo() {
+				try {
+					const params = {
+						number: this.order.expressCode
+					};
+
+					// 如果是顺丰快递,使用lastFour字段
+					if (this.order.expressCompany === '顺丰快递' && this.order.lastFour) {
+						params.mobile = this.order.lastFour;
+					}
+
+					const res = await queryExpress(params);
+					if (res.code === 200) {
+						const data = JSON.parse(res.data).data;
+						console.log("res", res)
+						this.logisticsCompany = data.expressCompanyName;
+						// 转换物流轨迹数据格式为uview-plus steps所需格式
+						if (data.logisticsTraceDetails && Array.isArray(data.logisticsTraceDetails)) {
+							this.logisticsList = data.logisticsTraceDetails.reverse().map(item => ({
+								title: item.desc || '',
+								desc: item.time ? this.formatTimestamp(item.time) : ''
+							}));
+							this.stepsKey++
+						} else {
+							this.logisticsList = [];
+						}
+					} else {
+						this.logisticsList = [];
+						uni.showToast({
+							title: '获取物流信息失败',
+							icon: 'none'
+						});
+					}
+				} catch (e) {
+					this.logisticsList = [];
+					uni.showToast({
+						title: '获取物流信息失败',
+						icon: 'none'
+					});
+				}
+			},
+			formatTimestamp(timestamp) {
+				const date = new Date(Number(timestamp));
+				const year = date.getFullYear();
+				const month = String(date.getMonth() + 1).padStart(2, '0');
+				const day = String(date.getDate()).padStart(2, '0');
+				const hours = String(date.getHours()).padStart(2, '0');
+				const minutes = String(date.getMinutes()).padStart(2, '0');
+				return `${year}-${month}-${day} ${hours}:${minutes}`;
+			},
+			previewImage(current, urls) {
+				uni.previewImage({
+					current,
+					urls
+				});
+			},
+			async getDeliveryStats() {
+				try {
+					const res = await deliveryStats({
+						workOrderId: this.order.id
+					});
+					if (res.code === 200) {
+						this.stats = res.data;
+					} else {
+						uni.showToast({
+							title: '获取发货统计信息失败',
+							icon: 'none'
+						});
+					}
+				} catch (e) {
+					uni.showToast({
+						title: '获取发货统计信息失败',
+						icon: 'none'
+					});
+				}
+			},
+			goToDeliverRecord() {
+				uni.navigateTo({
+					url: `/pagesA/production/deliver-record?workOrderId=${this.order.id}`
+				});
+			},
+			async getOrderDetail() {
+				try {
+					const res = await orderDetail({
+						id: this.orderId
+					})
+					if (res.success && res.data) {
+						this.order = res.data.order
+						this.orderListVos = res.data.orderListVos || []
+						this.getLogisticsInfo();
+					}
+				} catch (e) {
+					this.$u.toast('获取订单详情失败')
+				}
+			},
+		},
+		onLoad(op) {
+			this.orderId = op.id;
+			this.getOrderDetail()
+		}
+	};
+</script>
+
+<style scoped>
+	.contact-section {
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: #fff;
+		padding: 20rpx 30rpx;
+		display: flex;
+		justify-content: space-around;
+
+		.contact-item {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding: 20rpx;
+
+			text {
+				font-size: 24rpx;
+				color: #666;
+				margin-top: 10rpx;
+			}
+		}
+	}
+
+	.container {
+		padding: 20rpx;
+		background-color: #f8f8f8;
+	}
+
+	.logistics-header {
+		background-color: #ffffff;
+		border-radius: 10rpx;
+		padding: 32rpx;
+		box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+		margin-bottom: 20rpx;
+	}
+
+	.company-info,
+	.tracking-info {
+		display: flex;
+		align-items: center;
+	}
+
+	.label {
+		color: #666;
+		font-size: 28rpx;
+		margin-right: 10rpx;
+	}
+
+	.value {
+		color: #333;
+		font-size: 28rpx;
+		font-weight: 500;
+	}
+
+	.copy-btn {
+		background: #229fff;
+		color: #fff;
+		border-radius: 20rpx;
+		padding: 6rpx 22rpx;
+		font-size: 26rpx;
+		margin-left: 8rpx;
+		box-shadow: 0 2rpx 6rpx rgba(34, 159, 255, 0.08);
+		cursor: pointer;
+		transition: background 0.2s;
+	}
+
+	.copy-btn:active {
+		background-color: #e5e5e5;
+	}
+
+	.logistics-trace {
+		min-height: calc(100vh - 20rpx - 32rpx - 20rpx - 20rpx);
+		/* 减去容器内边距、头部内边距、头部底部外边距和自身内边距 */
+		background-color: #ffffff;
+		border-radius: 10rpx;
+		padding: 20rpx;
+		box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+	}
+
+	.steps-item {
+		padding: 20rpx 0;
+	}
+
+	.steps-title {
+		font-size: 28rpx;
+		color: #333;
+		margin-bottom: 10rpx;
+	}
+
+	.steps-time {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.no-data {
+		text-align: center;
+		padding: 40rpx;
+		color: #999;
+		font-size: 28rpx;
+	}
+
+	.goods-photos {
+		padding: 20rpx;
+		background-color: #fff;
+		border-radius: 12rpx;
+	}
+
+	.photo-section {
+		margin-bottom: 30rpx;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.photo-section:last-child {
+		margin-bottom: 0;
+	}
+
+	.photo-section .section-title {
+		font-size: 28rpx;
+		color: #333;
+		font-weight: bold;
+		margin-bottom: 20rpx;
+	}
+
+	.photo-section .photo-grid {
+		display: flex;
+		flex-wrap: nowrap;
+		gap: 20rpx;
+		overflow-x: auto;
+		padding-bottom: 10rpx;
+	}
+
+	.photo-section .photo-grid .photo-item {
+		width: 200rpx;
+		height: 200rpx;
+		flex-shrink: 0;
+		border-radius: 8rpx;
+		background-color: #f5f5f5;
+	}
+
+	.delivery-stats {
+		background-color: #ffffff;
+		border-radius: 10rpx;
+		padding: 20rpx;
+		box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+		margin-bottom: 20rpx;
+	}
+
+	.stats-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding-bottom: 20rpx;
+		border-bottom: 1rpx solid #eee;
+	}
+
+	.stats-title {
+		font-size: 30rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.stats-content {
+		padding-top: 20rpx;
+		display: grid;
+		grid-template-columns: repeat(2, 1fr);
+		gap: 20rpx;
+	}
+
+	.stats-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 10rpx;
+	}
+
+	.stats-label {
+		color: #666;
+		font-size: 26rpx;
+		margin-bottom: 8rpx;
+	}
+
+	.stats-value {
+		color: #229fff;
+		font-size: 32rpx;
+		font-weight: bold;
+	}
+
+	.record-btn {
+		display: flex;
+		align-items: center;
+		background: #ecf5ff;
+		padding: 10rpx 20rpx;
+		border-radius: 30rpx;
+		cursor: pointer;
+	}
+
+	.record-btn text {
+		color: #229fff;
+		font-size: 26rpx;
+		margin-left: 8rpx;
+	}
+</style>

+ 396 - 0
packageOrder/pages/payment/index.vue

@@ -0,0 +1,396 @@
+<template>
+	<view class="payment-container">
+		<!-- 收货地址 -->
+		<view class="address-section">
+			<view class="address-content" v-if="orderInfo">
+				<view class="contact-info">
+					<text class="name">{{orderInfo.name}}</text>
+					<text class="phone">{{orderInfo.mobile}}</text>
+				</view>
+				<view class="address-detail">
+					{{orderInfo.province}}{{orderInfo.city}}{{orderInfo.area}}{{orderInfo.address}}
+				</view>
+			</view>
+		</view>
+
+		<!-- 商品信息 -->
+		<view class="goods-section">
+			<view class="goods-title">商品信息</view>
+			<view class="goods-item" v-for="(item, index) in orderDetails" :key="index">
+				<image :src="item.productImage" mode="aspectFill" class="goods-img"></image>
+				<view class="goods-info">
+					<view class="goods-name">{{item.productName}}</view>
+					<view class="goods-spec">{{item.skuName}}</view>
+					<view class="price-num">
+						<text class="price">¥{{item.unitPrice}}</text>
+						<text class="num">x{{item.quantity}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 支付方式 -->
+		<view class="pay-section">
+			<view class="pay-title">支付方式</view>
+			<view class="pay-method">
+				<view class="method-item">
+					<image src="/static/images/wxpay.png" mode="aspectFit" class="pay-icon"></image>
+					<text>微信支付</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单金额信息 -->
+		<view class="amount-section">
+			<view class="amount-item">
+				<text>商品总价</text>
+				<text>¥{{orderInfo.paymentPrice}}</text>
+			</view>
+			<view class="amount-item total">
+				<text>实付金额</text>
+				<text class="total-price">¥{{orderInfo.paymentPrice}}</text>
+			</view>
+		</view>
+
+		<!-- 底部支付按钮 -->
+		<view class="footer">
+			<view class="total-section">
+				<text>实付金额:</text>
+				<text class="total-price">¥{{orderInfo.paymentPrice}}</text>
+			</view>
+			<view class="pay-btn" @click="handlePay">
+				立即支付
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getOrderPayDetail,
+		getWxPayParams
+	} from '@/config/api.js';
+
+	export default {
+		data() {
+			return {
+				orderId: '',
+				orderInfo: {},
+				orderDetails: []
+			}
+		},
+		onLoad(options) {
+			if (options.orderId) {
+				console.log(this.orderId, "ORDER")
+				this.orderId = options.orderId;
+				this.getOrderDetail();
+			} else {
+				uni.showToast({
+					title: '订单参数错误',
+					icon: 'none'
+				});
+				setTimeout(() => {
+					uni.navigateBack();
+				}, 1500);
+			}
+		},
+		methods: {
+			// 获取订单详情
+			async getOrderDetail() {
+				try {
+					uni.showLoading({
+						title: '加载中...'
+					});
+
+					const res = await getOrderPayDetail(this.orderId);
+					if (res.code === 200 && res.data) {
+						this.orderInfo = res.data.orderInfo.order;
+						this.orderDetails = res.data.orderDetails;
+
+						// 如果订单状态不是待付款,提示并返回
+						if (this.orderInfo.status !== 1) {
+							uni.showToast({
+								title: '订单状态已改变',
+								icon: 'none'
+							});
+							setTimeout(() => {
+								uni.navigateBack();
+							}, 1500);
+						}
+					} else {
+						uni.showToast({
+							title: res.msg || '获取订单信息失败',
+							icon: 'none'
+						});
+					}
+				} catch (e) {
+					console.error('获取订单详情失败:', e);
+					uni.showToast({
+						title: '获取订单信息失败',
+						icon: 'none'
+					});
+				} finally {
+					uni.hideLoading();
+				}
+			},
+
+			// 处理支付
+			async handlePay() {
+				if (!this.orderInfo) {
+					return uni.showToast({
+						title: '订单信息错误',
+						icon: 'none'
+					});
+				}
+
+				try {
+					// 调用后端获取支付参数
+					const payRes = await getWxPayParams(this.orderId);
+					if (payRes.code !== 200 || !payRes.data) {
+						throw new Error(payRes.msg || '获取支付参数失败');
+					}
+
+					// 调起微信支付
+					uni.requestPayment({
+						provider: 'wxpay',
+						timeStamp: payRes.data.timestamp,
+						nonceStr: payRes.data.noncestr,
+						package: payRes.data.package,
+						signType: payRes.data.signType,
+						paySign: payRes.data.sign,
+						success: (res) => {
+							uni.showToast({
+								title: '支付成功',
+								icon: 'success'
+							});
+							// 支付成功后跳转到订单列表
+							setTimeout(() => {
+								uni.redirectTo({
+									url: '/packageOrder/pages/list/index?status=2'
+								});
+							}, 1500);
+						},
+						fail: (err) => {
+							console.error('支付失败:', err);
+							uni.showToast({
+								title: '支付失败',
+								icon: 'none'
+							});
+							setTimeout(() => {
+								uni.redirectTo({
+									url: '/packageOrder/pages/list/index?status=1'
+								});
+							}, 1000);
+						}
+					});
+				} catch (e) {
+					console.error('支付异常:', e);
+					uni.showToast({
+						title: e.message || '支付失败',
+						icon: 'none'
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.payment-container {
+		min-height: 100vh;
+		background-color: #f8f8f8;
+		padding-bottom: 120rpx;
+	}
+
+	.address-section {
+		background-color: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+
+		.address-content {
+			.contact-info {
+				margin-bottom: 10rpx;
+
+				.name {
+					font-size: 32rpx;
+					font-weight: bold;
+					margin-right: 20rpx;
+				}
+
+				.phone {
+					font-size: 28rpx;
+					color: #666;
+				}
+			}
+
+			.address-detail {
+				font-size: 28rpx;
+				color: #333;
+				line-height: 1.4;
+			}
+		}
+	}
+
+	.goods-section {
+		background-color: #fff;
+		margin-bottom: 20rpx;
+
+		.goods-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			padding: 30rpx;
+			border-bottom: 1rpx solid #f5f5f5;
+		}
+
+		.goods-item {
+			display: flex;
+			padding: 30rpx;
+			border-bottom: 1rpx solid #f5f5f5;
+
+			.goods-img {
+				width: 160rpx;
+				height: 160rpx;
+				border-radius: 8rpx;
+				margin-right: 20rpx;
+			}
+
+			.goods-info {
+				flex: 1;
+
+				.goods-name {
+					font-size: 28rpx;
+					color: #333;
+					margin-bottom: 10rpx;
+				}
+
+				.goods-spec {
+					font-size: 24rpx;
+					color: #999;
+					margin-bottom: 20rpx;
+				}
+
+				.price-num {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+
+					.price {
+						font-size: 32rpx;
+						color: #ff4b4b;
+						font-weight: bold;
+					}
+
+					.num {
+						font-size: 28rpx;
+						color: #999;
+					}
+				}
+			}
+		}
+	}
+
+	.pay-section {
+		background-color: #fff;
+		margin-bottom: 20rpx;
+
+		.pay-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			padding: 30rpx;
+			border-bottom: 1rpx solid #f5f5f5;
+		}
+
+		.pay-method {
+			padding: 30rpx;
+
+			.method-item {
+				display: flex;
+				align-items: center;
+
+				.pay-icon {
+					width: 48rpx;
+					height: 48rpx;
+					margin-right: 20rpx;
+				}
+
+				text {
+					font-size: 28rpx;
+					color: #333;
+				}
+			}
+		}
+	}
+
+	.amount-section {
+		background-color: #fff;
+		padding: 30rpx;
+
+		.amount-item {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 20rpx;
+
+			text {
+				font-size: 28rpx;
+				color: #666;
+			}
+
+			&.total {
+				margin-top: 30rpx;
+				border-top: 1rpx solid #f5f5f5;
+				padding-top: 30rpx;
+
+				text {
+					font-size: 32rpx;
+					color: #333;
+					font-weight: bold;
+
+					&.total-price {
+						color: #ff4b4b;
+					}
+				}
+			}
+		}
+	}
+
+	.footer {
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		height: 100rpx;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		padding: 0rpx 30rpx;
+		box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+		.total-section {
+			flex: 1;
+
+			text {
+				font-size: 28rpx;
+				color: #333;
+
+				&.total-price {
+					font-size: 36rpx;
+					color: #ff4b4b;
+					font-weight: bold;
+				}
+			}
+		}
+
+		.pay-btn {
+			width: 240rpx;
+			height: 80rpx;
+			background-color: #F95B5B;
+			color: #fff;
+			font-size: 32rpx;
+			font-weight: bold;
+			border-radius: 40rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+	}
+</style>

+ 180 - 0
packageOrder/pages/score/index.vue

@@ -0,0 +1,180 @@
+<template>
+	<view>
+		<!-- 表单区域 -->
+		<view class="form-container region mar-t-10">
+			<view class="form-item">
+				<view class="form-label">评分</view>
+				<uni-rate size="30" v-model="score.score" />
+			</view>
+
+			<view class="form-item">
+				<view class="form-label">评价内容</view>
+				<view class="textarea-container">
+					<textarea style="width: 100%;" v-model="score.content" placeholder="请详细描述申请说明(选填)"
+						class="form-textarea" :maxlength="maxLength"></textarea>
+					<view class="char-count">
+						您还可以输入{{ remainingChars }} 字
+					</view>
+				</view>
+			</view>
+
+			<!-- 上传凭证 -->
+			<!-- 媒体上传 -->
+			<view class="upload-section">
+				<text class="section-title">图片上传</text>
+				<media-uploader v-model="mediaList" :maxCount="9" mediaType="image" :media="false"
+					:uploadUrl="uploadUrl" :token="token" @change="handleMediaChange" />
+			</view>
+		</view>
+
+		<!-- 提交按钮 -->
+		<button class="submit-button" @click="submitForm('customForm')">提交评价</button>
+	</view>
+</template>
+
+<script>
+	import {
+		UPLOAD_URL
+	} from '@/common/config.js';
+	import store from '@/store';
+	import MediaUploader from '@/components/MediaUploader.vue';
+	import {
+		saveScore
+	} from '@/config/api.js';
+
+	export default {
+		components: {
+			MediaUploader
+		},
+		data() {
+			return {
+				mediaList: [], // 媒体文件列表
+				uploadUrl: UPLOAD_URL, // 上传地址
+				token: "",
+				score: {
+					imgList: [],
+					score: 5,
+					content: ""
+				},
+				token: '',
+				imageValue: [],
+				fileList: [],
+				images: [],
+				orderId: "",
+				maxLength: 200, // 最大字数
+			};
+		},
+		computed: {
+			// 计算剩余字数
+			remainingChars() {
+				return this.maxLength - this.score.content.length;
+			}
+		},
+		onLoad(op) {
+			this.orderId = op.orderId;
+			this.token = uni.getStorageSync('access_token');
+		},
+		methods: {
+			// 处理媒体文件变化
+			handleMediaChange(list) {
+				console.log('媒体列表变化:', list);
+				// list 中的每个项目都包含 url 和 type (image/video)
+				this.mediaList = list;
+				// 更新 score.imgList,只包含图片类型的URL
+				this.score.imgList = list
+					.filter(item => item.type === 'image')
+					.map(item => item.url);
+			},
+
+			// 提交表单
+			submitForm() {
+				this.score.images = this.score.imgList.join(',');
+				this.score.orderId = this.orderId
+				saveScore(this.score).then((res) => {
+					if (res.code == 200) {
+
+						uni.showToast({
+							title: '评价成功',
+							icon: 'success'
+						});
+						setTimeout(() => {
+							uni.navigateBack({
+								delta: 1
+							})
+						}, 1000)
+					}
+				})
+
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.page-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		text-align: center;
+		padding: 20rpx 0;
+		color: #333;
+	}
+
+	.form-container {
+		padding: 20rpx;
+	}
+
+	.form-item {
+
+		margin-bottom: 20rpx;
+	}
+
+	.form-label {
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 10rpx;
+	}
+
+	.textarea-container {
+		position: relative;
+	}
+
+	.form-textarea {
+		border: 1px solid #ccc;
+		border-radius: 5rpx;
+		padding: 10rpx;
+		font-size: 28rpx;
+		height: 150rpx;
+		resize: none;
+	}
+
+	.char-count {
+		position: absolute;
+		bottom: 10rpx;
+		right: 10rpx;
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.upload-container {
+		margin-top: 10rpx;
+	}
+
+	.submit-button {
+		background-color: #F95B5B;
+		color: #fff;
+		font-size: 32rpx;
+		padding: 5rpx;
+		border-radius: 10rpx;
+		margin-left: 30rpx;
+		margin-right: 30rpx;
+		text-align: center;
+		margin-top: 30rpx;
+	}
+
+	.videoUpload {
+		border: 1rpx solid #999;
+
+		height: 200rpx;
+		width: 200rpx;
+	}
+</style>

+ 162 - 0
packageOrder/pages/score/product-score.vue

@@ -0,0 +1,162 @@
+<template>
+	<view class="evaluation-list">
+		<view class="flex-items mar-t-10 mar-b-32">
+			<view class="selectBg"
+				:style="selectValue==item.value ? {backgroundColor: '#F2D0A2' } : {backgroundColor: '#ebebeb' }"
+				v-for="(item, index) in select" :key="index" @click="selectRadio(item,index)">
+				<view class="font28">
+					{{item.label}}
+				</view>
+			</view>
+		</view>
+
+		<!-- 评价列表 -->
+		<view class="evaluation-item" v-for="(item, index) in scoreList" :key="index">
+			<!-- 用户信息 -->
+			<view class="user-info">
+				<image :src="item.avatar||'../../../static/images/avatar.png'" class="user-avatar"></image>
+				<view class="user-details">
+					<view class="user-name">{{ item.account }}</view>
+					<uni-rate activeColor="#E2A28A" size="16" :value="item.score" allow-half />
+				</view>
+				<view class="evaluation-date">{{ item.createTime }}</view>
+			</view>
+			<!-- 评价内容 -->
+			<view class="evaluation-content">{{ item.content }}</view>
+			<!-- 评价图片 -->
+			<view class="evaluation-images">
+				<view v-for="(img, imgIndex) in item.imgList" :key="imgIndex">
+					<image v-if="img" :src="img" class="evaluation-image" mode="aspectFit"
+						@click="previewImage(item.imgList, imgIndex)">
+					</image>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		scoreList
+	} from '@/config/api.js';
+	export default {
+		data() {
+			return {
+				selectValue: 1,
+				select: [{
+						label: "全部",
+						value: 1
+					},
+					{
+						label: "图片",
+						value: 2
+					}
+				],
+				params: {
+					current: 1,
+					size: 10,
+					productId: "",
+					images: 1
+				},
+				scoreList: []
+			};
+		},
+		onLoad(op) {
+			this.params.productId = op.productId;
+			this.getScoreList();
+		},
+		methods: {
+			selectRadio(item, index) {
+				this.selectValue = item.value;
+				this.params.images = this.selectValue;
+				this.getScoreList();
+			},
+			getScoreList() {
+				uni.showLoading({
+					title: '加载中...',
+					mask: true
+				});
+				scoreList(this.params).then((res) => {
+					this.scoreList = res.data.records;
+					this.scoreList.forEach(item => {
+						item.imgList = item.images.split(",");
+					});
+					uni.hideLoading();
+				});
+			},
+			// 预览图片
+			previewImage(imgList, currentIndex) {
+				uni.previewImage({
+					current: currentIndex, // 当前图片的索引
+					urls: imgList // 预览的图片列表
+				});
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.selectBg {
+		margin-right: 30rpx;
+		padding: 10px;
+		font-size: 28rpx;
+		border-radius: 5px;
+	}
+
+	.evaluation-list {
+		padding: 15px;
+		background-color: white;
+		min-height: 100vh;
+	}
+
+	.evaluation-item {
+		margin-bottom: 20px;
+		border-bottom: 1px solid #eee;
+		padding-bottom: 20px;
+	}
+
+	.user-info {
+		display: flex;
+		align-items: center;
+		margin-bottom: 10px;
+	}
+
+	.user-avatar {
+		width: 40px;
+		height: 40px;
+		border-radius: 50%;
+		margin-right: 10px;
+	}
+
+	.user-details {
+		flex: 1;
+	}
+
+	.user-name {
+		font-size: 14px;
+		font-weight: bold;
+	}
+
+	.evaluation-date {
+		font-size: 12px;
+		color: #999;
+	}
+
+	.evaluation-content {
+		font-size: 14px;
+		margin-bottom: 10px;
+	}
+
+	.evaluation-images {
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.evaluation-image {
+		width: 80px;
+		height: 80px;
+		margin-right: 10px;
+		margin-bottom: 10px;
+		cursor: pointer;
+	}
+</style>

+ 166 - 0
packageShop/pages/address-edit/index.vue

@@ -0,0 +1,166 @@
+<template>
+  <view class="address-edit">
+    <u-form :model="form" ref="form">
+      <u-form-item label="收货人" prop="name" required>
+        <u-input v-model="form.name" placeholder="请输入收货人姓名" />
+      </u-form-item>
+      
+      <u-form-item label="手机号码" prop="phone" required>
+        <u-input v-model="form.phone" placeholder="请输入手机号码" type="number" maxlength="11" />
+      </u-form-item>
+      
+      <u-form-item label="所在地区" prop="region" required @click="showRegionPicker = true">
+        <view class="region-select">
+          <text v-if="form.province">{{form.province + form.city + form.district}}</text>
+          <text v-else class="placeholder">请选择所在地区</text>
+          <u-icon name="arrow-right"></u-icon>
+        </view>
+      </u-form-item>
+      
+      <u-form-item label="详细地址" prop="address" required>
+        <u-textarea v-model="form.address" placeholder="请输入详细地址信息" />
+      </u-form-item>
+      
+      <u-form-item>
+        <u-switch v-model="form.isDefault" activeColor="#D93025">
+          <text slot="label" class="switch-label">设为默认地址</text>
+        </u-switch>
+      </u-form-item>
+    </u-form>
+
+    <view class="submit-btn">
+      <u-button type="primary" @click="submit" color="#D93025">保存</u-button>
+    </view>
+
+    <u-picker
+      :show="showRegionPicker"
+      :columns="regionColumns"
+      @confirm="confirmRegion"
+      @cancel="showRegionPicker = false"
+      title="选择地区">
+    </u-picker>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      form: {
+        name: '',
+        phone: '',
+        province: '',
+        city: '',
+        district: '',
+        address: '',
+        isDefault: false
+      },
+      showRegionPicker: false,
+      regionColumns: [
+        // 这里需要接入省市区数据
+        // 可以使用第三方包或者自行维护数据
+      ],
+      rules: {
+        name: [{
+          required: true,
+          message: '请输入收货人姓名',
+          trigger: ['blur', 'change']
+        }],
+        phone: [{
+          required: true,
+          message: '请输入手机号码',
+          trigger: ['blur', 'change']
+        }, {
+          pattern: /^1[3-9]\d{9}$/,
+          message: '请输入正确的手机号码',
+          trigger: ['blur', 'change']
+        }],
+        region: [{
+          required: true,
+          message: '请选择所在地区',
+          trigger: ['blur', 'change']
+        }],
+        address: [{
+          required: true,
+          message: '请输入详细地址',
+          trigger: ['blur', 'change']
+        }]
+      }
+    }
+  },
+  onLoad(options) {
+    if (options.id) {
+      this.getAddressDetail(options.id)
+    }
+  },
+  methods: {
+    async getAddressDetail(id) {
+      try {
+        const res = await this.$api.address.detail(id)
+        Object.assign(this.form, res.data)
+      } catch (e) {
+        this.$u.toast('获取地址信息失败')
+      }
+    },
+    confirmRegion(e) {
+      const [province, city, district] = e.value
+      this.form.province = province
+      this.form.city = city
+      this.form.district = district
+      this.showRegionPicker = false
+    },
+    async submit() {
+      try {
+        await this.$refs.form.validate()
+        const api = this.form.id ? 'update' : 'add'
+        await this.$api.address[api](this.form)
+        this.$u.toast(this.form.id ? '修改成功' : '添加成功')
+        setTimeout(() => {
+          uni.navigateBack()
+        }, 1500)
+      } catch (e) {
+        if (e.errors) return
+        this.$u.toast(this.form.id ? '修改失败' : '添加失败')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.address-edit {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 20rpx;
+  
+  :deep(.u-form) {
+    background: #fff;
+    border-radius: 12rpx;
+    padding: 20rpx;
+    
+    .u-form-item {
+      padding: 20rpx 0;
+    }
+  }
+  
+  .region-select {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    
+    .placeholder {
+      color: #c0c4cc;
+    }
+  }
+  
+  .switch-label {
+    font-size: 28rpx;
+    margin-right: 20rpx;
+  }
+  
+  .submit-btn {
+    margin-top: 40rpx;
+    padding: 0 20rpx;
+  }
+}
+</style> 

+ 178 - 0
packageShop/pages/address/index.vue

@@ -0,0 +1,178 @@
+<template>
+  <view class="address-container">
+    <u-empty v-if="addressList.length === 0" mode="address" icon="/static/images/empty/address.png">
+      <view class="empty-tips">暂无收货地址</view>
+    </u-empty>
+    
+    <view v-else class="address-list">
+      <view v-for="(item, index) in addressList" :key="index" class="address-item">
+        <view class="address-info" @click="selectAddress(item)">
+          <view class="user-info">
+            <text class="name">{{item.name}}</text>
+            <text class="phone">{{item.phone}}</text>
+            <text v-if="item.isDefault" class="default-tag">默认</text>
+          </view>
+          <view class="address-detail">{{item.province}}{{item.city}}{{item.district}}{{item.address}}</view>
+        </view>
+        <view class="operation">
+          <view class="edit" @click="editAddress(item)">
+            <u-icon name="edit-pen" size="40rpx"></u-icon>
+          </view>
+          <view class="delete" @click="deleteAddress(item)">
+            <u-icon name="trash" size="40rpx"></u-icon>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="add-btn">
+      <u-button type="primary" text="新增收货地址" @click="addAddress" color="#D93025"></u-button>
+    </view>
+
+    <u-modal
+      :show="showDeleteModal"
+      @confirm="confirmDelete"
+      @cancel="showDeleteModal = false"
+      title="删除确认"
+      content="确定要删除该收货地址吗?"
+      showCancelButton>
+    </u-modal>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      addressList: [],
+      showDeleteModal: false,
+      currentAddress: null
+    }
+  },
+  onShow() {
+    this.getAddressList()
+  },
+  methods: {
+    async getAddressList() {
+      try {
+        const res = await this.$api.address.list()
+        this.addressList = res.data || []
+      } catch (e) {
+        this.$u.toast('获取地址列表失败')
+      }
+    },
+    addAddress() {
+      uni.navigateTo({
+        url: '/packageShop/pages/address-edit/index'
+      })
+    },
+    editAddress(item) {
+      uni.navigateTo({
+        url: '/packageShop/pages/address-edit/index?id=' + item.id
+      })
+    },
+    deleteAddress(item) {
+      this.currentAddress = item
+      this.showDeleteModal = true
+    },
+    async confirmDelete() {
+      if (!this.currentAddress) return
+      try {
+        await this.$api.address.delete(this.currentAddress.id)
+        this.$u.toast('删除成功')
+        this.getAddressList()
+      } catch (e) {
+        this.$u.toast('删除失败')
+      }
+      this.showDeleteModal = false
+      this.currentAddress = null
+    },
+    selectAddress(item) {
+      const pages = getCurrentPages()
+      const prevPage = pages[pages.length - 2]
+      if (prevPage && prevPage.$vm.setSelectedAddress) {
+        prevPage.$vm.setSelectedAddress(item)
+        uni.navigateBack()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.address-container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 120rpx;
+  
+  .address-list {
+    padding: 20rpx;
+    
+    .address-item {
+      background: #fff;
+      border-radius: 12rpx;
+      padding: 30rpx;
+      margin-bottom: 20rpx;
+      
+      .address-info {
+        .user-info {
+          margin-bottom: 16rpx;
+          
+          .name {
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-right: 20rpx;
+          }
+          
+          .phone {
+            font-size: 28rpx;
+            color: #666;
+          }
+          
+          .default-tag {
+            background: #D93025;
+            color: #fff;
+            font-size: 20rpx;
+            padding: 4rpx 12rpx;
+            border-radius: 4rpx;
+            margin-left: 20rpx;
+          }
+        }
+        
+        .address-detail {
+          font-size: 28rpx;
+          color: #333;
+          line-height: 1.4;
+        }
+      }
+      
+      .operation {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 20rpx;
+        border-top: 1rpx solid #eee;
+        padding-top: 20rpx;
+        
+        .edit, .delete {
+          padding: 10rpx 20rpx;
+        }
+      }
+    }
+  }
+  
+  .add-btn {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 20rpx 40rpx;
+    background: #fff;
+  }
+  
+  .empty-tips {
+    font-size: 28rpx;
+    color: #999;
+    margin-top: 20rpx;
+  }
+}
+</style> 

+ 78 - 0
packageShop/pages/article/detail.vue

@@ -0,0 +1,78 @@
+<template>
+  <view class="article-container">
+    <!-- 标题 -->
+    <view class="article-title">{{title}}</view>
+    <!-- 时间 -->
+    <view class="article-time">{{updateTime}}</view>
+    <!-- 富文本内容展示 -->
+    <rich-text :nodes="content"></rich-text>
+  </view>
+</template>
+
+<script>
+import { indexmenuDetail } from '@/config/api.js';
+
+export default {
+  data() {
+    return {
+      content: '', // 富文本内容
+      title: '', // 文章标题
+      updateTime: '', // 更新时间
+      id: '' // 文章ID
+    }
+  },
+  
+  onLoad(options) {
+    if (options.id) {
+      this.id = options.id;
+      this.getArticleDetail();
+    }
+  },
+  
+  methods: {
+    // 获取文章详情
+    getArticleDetail() {
+      indexmenuDetail(this.id).then(res => {
+        if (res.code === 200) {
+          const detail = res.data;
+          this.title = detail.title;
+          this.content = detail.context;
+          this.updateTime = detail.updateTime;
+        } else {
+          uni.showToast({
+            title: res.msg || '获取文章详情失败',
+            icon: 'none'
+          });
+        }
+      }).catch(err => {
+        console.error('获取文章详情失败:', err);
+        uni.showToast({
+          title: '获取文章详情失败',
+          icon: 'none'
+        });
+      });
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.article-container {
+  padding: 30rpx;
+  background: #fff;
+  min-height: 100vh;
+}
+
+.article-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.article-time {
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 30rpx;
+}
+</style> 

+ 684 - 0
packageShop/pages/cart/index.vue

@@ -0,0 +1,684 @@
+<template>
+	<view class="cart-container">
+		<!-- 添加动画小球 -->
+		<view class="cart-ball" :class="{ active: ballShow }" :style="ballStyle" v-show="ballShow">
+			<view class="inner"></view>
+		</view>
+		<!-- 顶部导航栏 -->
+		<view class="cart-header">
+			<text class="header-title"></text>
+			<text class="manage-btn" @tap="toggleManageMode">{{isManageMode ? '完成' : '管理'}}</text>
+		</view>
+
+		<!-- 购物车为空时显示 -->
+		<view class="empty-cart" v-if="list.length === 0">
+			<image src="/static/empty-cart.png" mode="aspectFit"></image>
+			<view class="flex-items-plus">
+				<image src="/static/images/empty.png" class="empty"></image>
+			</view>
+			<view class="empty-text">购物车是空的</view>
+			<button class="go-shopping-btn" @tap="goShopping">去逛逛</button>
+		</view>
+
+		<!-- 购物车有商品时显示 -->
+		<view class="cart-content" v-else>
+			<!-- 商品列表 -->
+			<view class="cart-list">
+				<view class="cart-item" :class="{ 'out-of-stock': !item.stock || item.stock <= 0 }" v-for="(item, index) in list" :key="index">
+					<checkbox :checked="item.selected" @tap="toggleSelect(index)" :color="'#ff6b81'"></checkbox>
+					<view class="item-content" @tap="goToProductDetail(item)">
+						<image :src="item.images" mode="aspectFill"></image>
+						<view class="item-info">
+							<view class="title">{{item.productName}}</view>
+							<view class="sku-name">
+								<text>
+									规格:
+								</text>
+								<text>
+									{{item.skuName}}
+								</text>
+							</view>
+							<view class="stock-info">
+								<text class="stock-text">库存:{{item.stock || 0}}件</text>
+								<text class="stock-warning" v-if="!item.stock || item.stock <= 0">(库存不足)</text>
+							</view>
+							<view class="price-quantity">
+								<text class="price">¥{{item.price}}</text>
+								<view class="quantity-control" v-if="!isManageMode" @tap.stop="">
+									<text class="minus" @tap="decreaseQuantity(index)">-</text>
+									<input type="number" v-model="item.count" @blur="updateQuantity(index)" />
+									<text class="plus" 
+										:class="{ 'disabled': !item.stock || item.stock <= 0 || item.count >= item.stock }"
+										@tap="increaseQuantity(index)">+</text>
+								</view>
+							</view>
+						</view>
+					</view>
+					<view class="delete-icon" @tap="deleteItem(index)" v-if="!isManageMode">
+						<uni-icons type="close" size="28"></uni-icons>
+					</view>
+				</view>
+			</view>
+
+			<!-- 底部结算栏 -->
+			<view class="cart-footer">
+				<view class="select-all">
+					<checkbox :checked="isAllSelected" @tap="toggleSelectAll" :color="'#ff6b81'"></checkbox>
+					<text>全选</text>
+				</view>
+				<view class="right-section" v-if="!isManageMode">
+					<view class="total-info">
+						<text>合计:</text>
+						<text class="total-price">¥{{totalPrice}}</text>
+					</view>
+					<button class="checkout-btn" @tap="checkout">结算({{selectedCount}})</button>
+				</view>
+				<view class="right-section" v-else>
+					<view class="total-info">
+						<text>已选:</text>
+						<text class="selected-count">{{selectedCount}}件</text>
+					</view>
+					<button class="delete-btn delete-btn-outline" @tap="batchDelete">删除</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		productCartList,
+		productCartCount,
+		productCartSave,
+		productCartRemoveIds,
+		saveCartOrder,
+		productCartTotalPrice
+	} from '../../../config/api.js';
+	import {
+		mapGetters
+	} from 'vuex';
+
+	export default {
+		computed: {
+			...mapGetters(['isLogin'])
+		},
+		data() {
+			return {
+				list: [], // 购物车商品列表
+				isLoading: false, // 是否正在加载
+				noMoreData: false, // 是否没有更多数据
+				isManageMode: false, // 是否为管理模式
+				params: {
+					current: 1,
+					size: 10
+				},
+				totalPrice: '0.00', // 购物车总价
+				// 添加小球动画相关数据
+				ballShow: false,
+				ballStyle: {
+					left: '0px',
+					top: '0px'
+				}
+			}
+		},
+		computed: {
+			// 是否全选
+			isAllSelected() {
+				return this.list.length > 0 && this.list.every(item => item.selected);
+			},
+			// 已选商品数量
+			selectedCount() {
+				return this.list.filter(item => item.selected).length;
+			},
+			// 计算当前总价
+			currentTotalPrice() {
+				return this.list.reduce((total, item) => {
+					if (item.selected) {
+						return total + (Number(item.price) * Number(item.count));
+					}
+					return total;
+				}, 0).toFixed(2);
+			}
+		},
+		onShow() {
+			// // 页面显示时获取购物车数据
+			// if (this.isLogin) {
+			this.getList()
+			// } else {
+			// 	this.$Router.push({
+			// 		path: '/pages/user/login'
+			// 	})
+			// }
+		},
+		onReachBottom() {
+			if (!this.isLoading && !this.noMoreData) {
+				this.getList();
+			}
+		},
+		methods: {
+			// 获取购物车列表
+			async getList() {
+				if (this.isLoading) return;
+				this.isLoading = true;
+
+				try {
+					const res = await productCartList(this.params);
+					if (res.code === 200) {
+						const records = res.data.records || [];
+						records.forEach(item => {
+							item.selected = false; // 添加选中状态属性
+						});
+
+						if (this.params.current === 1) {
+							this.list = records;
+						} else {
+							this.list = [...this.list, ...records];
+						}
+
+						this.noMoreData = records.length < this.params.size;
+						this.params.current++;
+
+						// 获取购物车总价
+						this.getTotalPrice();
+					}
+				} catch (error) {
+					console.error('获取购物车列表失败:', error);
+					uni.$u.toast('获取购物车列表失败');
+				} finally {
+					this.isLoading = false;
+				}
+			},
+
+			// 获取购物车总价
+			async getTotalPrice() {
+				try {
+					// 由于我们现在基于选中状态计算价格,直接使用计算属性
+					this.totalPrice = this.currentTotalPrice;
+				} catch (error) {
+					console.error('获取购物车总价失败:', error);
+				}
+			},
+
+			// Toggle manage mode
+			toggleManageMode() {
+				this.isManageMode = !this.isManageMode;
+				// Reset selection when exiting manage mode
+				if (!this.isManageMode) {
+					this.list.forEach(item => item.selected = false);
+				}
+			},
+
+			// Batch delete selected items
+			async batchDelete() {
+				const selectedItems = this.list.filter(item => item.selected);
+				if (selectedItems.length === 0) {
+					uni.showToast({
+						title: '请选择要删除的商品',
+						icon: 'none'
+					});
+					return;
+				}
+
+				uni.showModal({
+					title: '提示',
+					content: '确定要删除选中的商品吗?',
+					success: async (res) => {
+						if (res.confirm) {
+							try {
+								const ids = selectedItems.map(item => item.id).join(',');
+								const res = await productCartRemoveIds(ids);
+								if (res.code === 200) {
+									// Remove deleted items from list
+									this.list = this.list.filter(item => !item.selected);
+									// 更新前端总价
+									this.totalPrice = this.currentTotalPrice;
+									uni.$u.toast('删除成功');
+									// Exit manage mode if no items left
+									if (this.list.length === 0) {
+										this.isManageMode = false;
+									}
+								}
+							} catch (error) {
+								console.error('批量删除失败:', error);
+								uni.$u.toast('删除失败');
+							}
+						}
+					}
+				});
+			},
+
+			// Toggle select item
+			toggleSelect(index) {
+				this.list[index].selected = !this.list[index].selected;
+				this.totalPrice = this.currentTotalPrice;
+			},
+
+			// Toggle select all
+			toggleSelectAll() {
+				const newStatus = !this.isAllSelected;
+				this.list.forEach(item => {
+					item.selected = newStatus;
+				});
+				this.totalPrice = this.currentTotalPrice;
+			},
+
+			// Delete single item
+			async deleteItem(index) {
+				uni.showModal({
+					title: '提示',
+					content: '确定要删除这个商品吗?',
+					success: async (res) => {
+						if (res.confirm) {
+							try {
+								const response = await productCartRemoveIds(this.list[index].id);
+								if (response.code === 200) {
+									// Remove item from list
+									this.list.splice(index, 1);
+									// 更新前端总价
+									this.totalPrice = this.currentTotalPrice;
+									uni.$u.toast('删除成功');
+								}
+							} catch (error) {
+								console.error('删除失败:', error);
+								uni.$u.toast('删除失败');
+							}
+						}
+					}
+				});
+			},
+
+			// Increase quantity
+			async increaseQuantity(index) {
+				const item = this.list[index];
+				const newCount = Number(item.count) + 1;
+				
+				// 检查库存
+				if (!item.stock || item.stock <= 0) {
+					uni.$u.toast('该商品库存不足');
+					return;
+				}
+				
+				if (newCount > item.stock) {
+					uni.$u.toast(`该商品库存仅剩${item.stock}件`);
+					return;
+				}
+				
+				await this.updateItemQuantity(index, newCount);
+			},
+
+			// Decrease quantity
+			async decreaseQuantity(index) {
+				const item = this.list[index];
+				if (Number(item.count) > 1) {
+					const newCount = Number(item.count) - 1;
+					await this.updateItemQuantity(index, newCount);
+				}
+			},
+
+			// Update quantity
+			async updateQuantity(index) {
+				const item = this.list[index];
+				const newCount = Number(item.count);
+				
+				if (newCount < 1) {
+					item.count = 1;
+					return;
+				}
+				
+				// 检查库存
+				if (!item.stock || item.stock <= 0) {
+					uni.$u.toast('该商品库存不足');
+					item.count = 0;
+					return;
+				}
+				
+				if (newCount > item.stock) {
+					uni.$u.toast(`该商品库存仅剩${item.stock}件,已调整为最大可购买数量`);
+					item.count = item.stock;
+					await this.updateItemQuantity(index, item.stock);
+					return;
+				}
+				
+				await this.updateItemQuantity(index, newCount);
+			},
+
+			// Update item quantity in backend
+			async updateItemQuantity(index, newCount) {
+				const item = this.list[index];
+				try {
+					const res = await productCartSave({
+						id: item.id,
+						count: newCount
+					});
+					if (res.code === 200) {
+						// Update item count
+						item.count = newCount;
+						// Update total price
+						this.getTotalPrice();
+					}
+				} catch (error) {
+					console.error('更新数量失败:', error);
+					uni.$u.toast('更新数量失败');
+				}
+			},
+
+			// Go shopping
+			goShopping() {
+				uni.switchTab({
+					url: '/pages/shop/product-type-list'
+				})
+			},
+
+			// 跳转到商品详情页
+			goToProductDetail(item) {
+				if (item.productId) {
+					uni.navigateTo({
+						url: `/packageShop/pages/detail/index?id=${item.productId}`
+					});
+				}
+			},
+
+			// Checkout
+			checkout() {
+				const selectedItems = this.list.filter(item => item.selected);
+				if (selectedItems.length === 0) {
+					uni.showToast({
+						title: '请选择要结算的商品',
+						icon: 'none'
+					});
+					return;
+				}
+
+				// 检查选中商品的库存
+				for (let item of selectedItems) {
+					if (!item.stock || item.stock <= 0) {
+						uni.showToast({
+							title: `商品"${item.productName}"库存不足,请先删除或更换`,
+							icon: 'none',
+							duration: 3000
+						});
+						return;
+					}
+					
+					if (Number(item.count) > item.stock) {
+						uni.showToast({
+							title: `商品"${item.productName}"数量超出库存,库存仅剩${item.stock}件`,
+							icon: 'none',
+							duration: 3000
+						});
+						return;
+					}
+				}
+
+				// 跳转到结算页面
+				uni.navigateTo({
+					url: `/packageShop/pages/settle/index?items=${encodeURIComponent(JSON.stringify(selectedItems))}`
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+	.cart-container {
+		min-height: 100vh;
+		background-color: #f8f8f8;
+		padding-bottom: 120rpx;
+	}
+
+	.cart-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20rpx 30rpx;
+		background-color: #fff;
+	}
+
+	.header-title {
+		font-size: 32rpx;
+		font-weight: bold;
+	}
+
+	.manage-btn {
+		font-size: 28rpx;
+		color: #666;
+	}
+
+	.empty-cart {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding-top: 200rpx;
+	}
+
+	.empty-cart image {
+		width: 200rpx;
+		height: 200rpx;
+		margin-bottom: 30rpx;
+	}
+
+	.empty-text {
+		font-size: 28rpx;
+		color: #999;
+		margin-bottom: 40rpx;
+	}
+
+	.go-shopping-btn {
+		width: 240rpx;
+		height: 80rpx;
+		line-height: 80rpx;
+		text-align: center;
+		background-color: #F95B5B;
+		color: #fff;
+		border-radius: 40rpx;
+		font-size: 28rpx;
+	}
+
+	.cart-list {
+		padding: 20rpx;
+	}
+
+	.cart-item {
+		display: flex;
+		align-items: center;
+		background-color: #fff;
+		padding: 20rpx;
+		margin-bottom: 20rpx;
+		border-radius: 12rpx;
+	}
+
+	.cart-item.out-of-stock {
+		background-color: #f9f9f9;
+		opacity: 0.8;
+	}
+
+	.cart-item.out-of-stock .item-content image {
+		opacity: 0.6;
+	}
+
+	.item-content {
+		display: flex;
+		flex: 1;
+		margin-left: 20rpx;
+		cursor: pointer;
+	}
+
+	.item-content:active {
+		opacity: 0.8;
+	}
+
+	.item-content image {
+		width: 160rpx;
+		height: 160rpx;
+		border-radius: 8rpx;
+	}
+
+	.item-info {
+		flex: 1;
+		margin-left: 20rpx;
+	}
+
+	.title {
+		font-size: 28rpx;
+		color: #333;
+		margin-bottom: 10rpx;
+	}
+
+	.sku-name {
+		font-size: 24rpx;
+		color: #999;
+		margin-bottom: 10rpx;
+	}
+
+	.stock-info {
+		font-size: 24rpx;
+		margin-bottom: 20rpx;
+	}
+
+	.stock-text {
+		color: #666;
+	}
+
+	.stock-warning {
+		color: #ff6b81;
+		font-weight: bold;
+	}
+
+	.price-quantity {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.price {
+		font-size: 32rpx;
+		color: #ff6b81;
+		font-weight: bold;
+	}
+
+	.quantity-control {
+		display: flex;
+		align-items: center;
+		border: 1rpx solid #eee;
+		border-radius: 6rpx;
+	}
+
+	.minus,
+	.plus {
+		width: 60rpx;
+		height: 60rpx;
+		line-height: 60rpx;
+		text-align: center;
+		font-size: 36rpx;
+		color: #666;
+	}
+
+	.plus.disabled {
+		color: #ccc;
+		background-color: #f5f5f5;
+	}
+
+	.quantity-control input {
+		width: 80rpx;
+		height: 60rpx;
+		text-align: center;
+		font-size: 28rpx;
+		border-left: 1rpx solid #eee;
+		border-right: 1rpx solid #eee;
+	}
+
+	.delete-icon {
+		padding: 20rpx;
+	}
+
+	.cart-footer {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 100rpx;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		padding: 0 30rpx;
+		box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+	}
+
+	.select-all {
+		display: flex;
+		align-items: center;
+	}
+
+	.select-all text {
+		font-size: 28rpx;
+		color: #333;
+		margin-left: 10rpx;
+	}
+
+	.right-section {
+		flex: 1;
+		display: flex;
+		justify-content: flex-end;
+		align-items: center;
+	}
+
+	.total-info {
+		margin-right: 30rpx;
+	}
+
+	.total-price {
+		font-size: 36rpx;
+		color: #ff6b81;
+		font-weight: bold;
+	}
+
+	.checkout-btn {
+		width: 200rpx;
+		height: 80rpx;
+		line-height: 80rpx;
+		text-align: center;
+		background-color: #F95B5B;
+		color: #fff;
+		border-radius: 40rpx;
+		font-size: 28rpx;
+	}
+
+	.delete-btn {
+		width: 200rpx;
+		height: 80rpx;
+		line-height: 80rpx;
+		text-align: center;
+		background-color: #ff6b81;
+		color: #fff;
+		border-radius: 40rpx;
+		font-size: 28rpx;
+	}
+
+	.delete-btn-outline {
+		background-color: #fff;
+		color: #ff6b81;
+		border: 1rpx solid #ff6b81;
+	}
+
+	/* 购物车动画小球样式 */
+	.cart-ball {
+		position: fixed;
+		left: -18px;
+		top: 0;
+		z-index: 999;
+		transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41);
+		opacity: 0;
+	}
+
+	.cart-ball.active {
+		opacity: 1;
+	}
+
+	.cart-ball .inner {
+		width: 16px;
+		height: 16px;
+		border-radius: 50%;
+		background-color: #ff6b81;
+		transition: all 0.4s linear;
+	}
+</style>

+ 945 - 0
packageShop/pages/detail/index.vue

@@ -0,0 +1,945 @@
+<!-- 商品的详情页 -->
+<template>
+	<view class="">
+		<u-navbar leftIconColor="#888888" autoBack :bgColor="bgColor" :title="navTitle"
+			titleStyle="color: #000000"></u-navbar>
+		<!-- 添加动画小球 -->
+		<view class="cart-ball" :class="{ active: ballShow }" :style="ballStyle" v-show="ballShow">
+			<view class="inner"></view>
+		</view>
+		<!-- 产品主图轮播 -->
+		<swiper class="swiper" :style="{height: '750rpx'}" :indicator-dots="true" :autoplay="true" :interval="3500"
+			@change="e => (currentNum = e.detail.current)">
+			<swiper-item v-for="(item, index) in imageList" :key="index">
+				<image :src="item" mode="aspectFill" style="width: 100%; height: 100%;"></image>
+			</swiper-item>
+		</swiper>
+
+		<!-- 商品信息 -->
+		<view class="product-info">
+			<view class="product-main">
+				<!-- 价格区域 -->
+				<view class="price-section">
+					<text class="current-price">¥{{detail.price}}</text>
+					<text class="price-suffix" v-if="skuNum != 0">起</text>
+					<text class="original-price">¥{{detail.originalPrice}}</text>
+				</view>
+
+				<!-- 商品名称 -->
+				<text class="product-name">{{detail.name}}</text>
+
+				<!-- 销量库存信息 -->
+				<view class="product-stats">
+					<text class="stat-item">销量 {{detail.salesTotal}}件</text>
+					<view class="divider"></view>
+					<text class="stat-item">剩余 {{detail.stock}}件</text>
+				</view>
+			</view>
+
+			<!-- 分享按钮 -->
+			<view class="share-section">
+				<button open-type="share" class="share-btn">
+					<u-icon name="share" size="22" label="分享" labelPos="bottom" labelSize="12"></u-icon>
+				</button>
+			</view>
+		</view>
+
+		<!-- 商品属性 -->
+		<view class="productStats">
+			<view class="mar-b-30">
+				<text class="font28">服务</text>
+				<text class="font28 font-666 mar-lr-32 flex-1">快递:免运费</text>
+			</view>
+			<view @click="skuPopUp">
+				<view class="mar-b-20 flex-items flex-sp-between">
+					<view>
+						<text class="font28">选择</text>
+						<text class="font28 font-666 mar-lr-32 flex-1">规格</text>
+					</view>
+					<view class="mar-r-8">
+						<view class="flex-items">
+							<view class="guige">
+								共{{skuNum}}种规格可选
+							</view>
+							<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+						</view>
+					</view>
+				</view>
+				<view class="mar-b-30 mar-lr-64 flex-items">
+					<view v-for="(item,index) in detail.sku">
+						<image class="skuImg" :src="item.skuImage" mode="aspectFit"></image>
+					</view>
+				</view>
+			</view>
+
+			<view>
+				<text class="font28">参数</text>
+				<text class="font28 font-666 mar-lr-32 flex-1">{{detail.parameter}}</text>
+			</view>
+		</view>
+
+		<!-- 评价 -->
+		<view class="productStats" @click="goScore">
+			<view class="flex-items flex-sp-between">
+				<view class="font30 font-bold">
+					评价 ({{scoreList.length}})
+				</view>
+				<view class="mar-r-16">
+					<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+				</view>
+			</view>
+
+			<view class="mar-b-30 mar-t-40" v-for="(item,index) in scoreList" :key="index">
+				<view class="flex-items flex-sp-between">
+					<view class="flex-items">
+						<view class="mar-r-14">
+							<image class="avatar" :src="item.avatar||'../../../static/images/avatar.png'"
+								mode="aspectFill">
+							</image>
+						</view>
+						<view class="font28">
+							{{item.account}}
+							<uni-rate activeColor="#E2A28A" size="16" :value="item.score" allow-half />
+						</view>
+					</view>
+					<view class="font-gray mar-r-14">
+						{{item.scoreTime}}
+					</view>
+				</view>
+				<view class="mar-t-30 font28 mar-l-80">
+					{{item.content}}
+				</view>
+				<view class="flex-items mar-l-80">
+					<view class="mar-t-30 font28 mar-r-14" v-for="(item,index) in item.imgList">
+						<image v-if="item&&index<3" class="scoreImg" :src="item" mode="aspectFit"></image>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 详情和规格 -->
+		<view class="bg-white font32 pad-32 text-align font-gray">
+			—— 商品详情 ——
+		</view>
+		<view class="bg-white">
+			<u-parse :content="detail.context" :tagStyle="style"></u-parse>
+		</view>
+		<view class="height104"></view>
+
+		<!-- 底部操作栏 -->
+		<view class="navigation">
+			<view class="flex">
+				<view class="left">
+					<view class="item" @tap="goOrder">
+						<u-icon name="order" size="22"></u-icon>
+						<view class="text u-line-1">订单</view>
+					</view>
+					<view class="item" @tap="goCart">
+						<view class="cart-icon-wrapper">
+							<u-icon name="shopping-cart" size="24"></u-icon>
+							<view class="cart-badge" v-if="cartCount > 0">{{cartCount}}</view>
+						</view>
+						<view class="text u-line-1">购物车</view>
+					</view>
+					<view class="item" @click="handlerCollect">
+						<u-icon :name="isCollect? 'star-fill' :'star'" :color="isCollect? '#F95B5B' : ''"
+							size="22"></u-icon>
+						<view class="text u-line-1">收藏</view>
+					</view>
+				</view>
+				<view class="right">
+					<view class="cart cartBtn u-line-1" :class="{ 'disabled': !detail.stock || detail.stock <= 0 }"
+						@click="addCart">加入购物车</view>
+					<view class="buy buyBtn u-line-1" :class="{ 'disabled': !detail.stock || detail.stock <= 0 }"
+						@click="goPay">立即购买</view>
+				</view>
+			</view>
+
+			<!-- 规格选择弹窗 -->
+			<uni-popup borderRadius="20rpx 20rpx 0 0" ref="popup" background-color="#fff">
+				<view class="popup">
+					<view class="flex-items">
+						<view>
+							<image class="prpupImg" :src="skuSelect.skuImage" mode="aspectFit"
+								@click="previewImage(skuSelect.skuImage)"></image>
+						</view>
+						<view class="mar-l-20">
+							<view class="font32 pad-b-24">
+								{{detail.name}}
+							</view>
+							<view class="pad-t-10">
+								<text class="font40 font-red font-bold">¥{{skuSelect.price}}</text>
+							</view>
+							<view class="pad-t-15">
+								<text class="font28 font-gray">剩余{{skuSelect.skuStock}}件</text>
+							</view>
+						</view>
+					</view>
+					<!-- 线条 -->
+					<view class="gray-line mar-tb-8"></view>
+					<view class="font38 font-bold mar-tb-20">
+						请选择规格
+					</view>
+					<view class="flex-container">
+						<view class="hotBg"
+							:style="selectId==item.id ? {backgroundColor: '#F2D0A2' } : {backgroundColor: '#ebebeb' }"
+							v-for="(item, index) in detail.sku" :key="index" @click="selectRadio(item,index)">
+							<view class="font28 ">
+								{{item.skuName}}
+							</view>
+						</view>
+					</view>
+
+					<view class="flex-items flex-sp-between mar-b-60">
+						<view class="mar-l-16 font32">
+							购买数量
+						</view>
+						<view class="mar-r-16">
+							<u-number-box v-model="count"></u-number-box>
+						</view>
+						<button class="fixed-button" @click="goSettle">{{popupText}}</button>
+					</view>
+				</view>
+			</uni-popup>
+			<u-safe-bottom></u-safe-bottom>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		mapMutations,
+		mapActions,
+		mapGetters
+	} from 'vuex';
+	import {
+		productCartCount,
+		productDetail,
+		productAddCollect,
+		productClearCollect,
+		scoreList,
+		productCartSave
+	} from '../../../config/api.js';
+	import {
+		MAIN_COLOR
+	} from '../../../common/config.js'
+	export default {
+		computed: {
+			...mapGetters(['isLogin']),
+			navTitle() {
+				return this.bgColor !== 'transparent' ? '商品详情' : ''
+			},
+			navIconColor() {
+				return this.bgColor !== 'transparent' ? '#FFFFFF' : '#cecece'
+			}
+		},
+		data() {
+			return {
+				count: 1,
+				skuNum: 0,
+				popupText: "立即购买",
+				selectId: "",
+				skuSelect: {},
+				bgColor: 'transparent',
+				currentSkuText: '',
+				currentNum: 0,
+				buyType: 'sku',
+				params: {
+					current: 1,
+					size: 2,
+					productId: "",
+					images: 1
+				},
+				imgList: [],
+				isCollect: false,
+				list: ['商品详情'],
+				curNow: 0,
+				radios: [],
+				detail: {},
+				imageList: [],
+				scoreList: [],
+				user: {},
+				cartCount: 0,
+				style: {
+					img: 'width: 100%; vertical-align: top;',
+				},
+				showShare: false,
+				ballShow: false,
+				ballStyle: {
+					left: '0px',
+					top: '0px'
+				}
+			};
+		},
+		onPageScroll(e) {
+			if (e.scrollTop >= 100) {
+				this.bgColor = "#FFF"
+			} else {
+				this.bgColor = 'transparent'
+			}
+		},
+		onLoad(opt) {
+			this.params.productId = opt.id
+			this.getDetail(this.params.productId)
+			this.getScoreList()
+			if (this.isLogin) {
+				this.getCartCount()
+			}
+			setTimeout(() => {
+				if (opt.skuId) {
+					this.onceOrder(opt.skuId)
+				}
+			}, 300)
+		},
+		// 分享功能
+		onShareAppMessage() {
+			return {
+				title: this.detail.name || '商品详情',
+				path: `/packageShop/pages/detail/index?id=${this.params.productId}`,
+				imageUrl: this.imageList && this.imageList.length > 0 ? this.imageList[0] : ''
+			}
+		},
+		methods: {
+			goCart() {
+				if (!this.isLogin) {
+					uni.navigateTo({
+						url: '/packageUser/pages/login/index'
+					});
+					return;
+				}
+				this.$route('/packageShop/pages/cart/index')
+			},
+			goOrder() {
+				if (!this.isLogin) {
+					uni.navigateTo({
+						url: '/packageUser/pages/login/index'
+					});
+					return;
+				}
+				this.$route('/packageOrder/pages/list/index?status=0')
+			},
+			getCartCount() {
+				if (!this.isLogin) return;
+				productCartCount().then((res) => {
+					this.cartCount = res.data
+				})
+			},
+			goSettle() {
+				if (this.buyType === 'buy') {
+					if (!this.skuSelect || !this.skuSelect.id) {
+						return uni.$u.toast('请选择商品规格');
+					}
+					// 检查选中规格的库存
+					if (!this.skuSelect.skuStock || this.skuSelect.skuStock <= 0) {
+						return uni.$u.toast('该规格库存不足,无法购买');
+					}
+					// 检查购买数量是否超过库存
+					if (this.count > this.skuSelect.skuStock) {
+						return uni.$u.toast(`该规格仅剩${this.skuSelect.skuStock}件,请调整购买数量`);
+					}
+					if (!this.isLogin) {
+						uni.navigateTo({
+							url: '/packageUser/pages/login/index'
+						});
+						return;
+					}
+					this.skuSelect.count = this.count;
+					this.skuSelect.productName = this.detail.name;
+					const sku = JSON.stringify(this.skuSelect);
+					uni.navigateTo({
+						url: `/packageShop/pages/settle/index?sku=${encodeURIComponent(sku)}`,
+					});
+				} else {
+					if (!this.skuSelect || !this.skuSelect.id) {
+						return uni.$u.toast('请选择商品规格');
+					}
+					// 检查选中规格的库存
+					if (!this.skuSelect.skuStock || this.skuSelect.skuStock <= 0) {
+						return uni.$u.toast('该规格库存不足,无法加入购物车');
+					}
+					// 检查购买数量是否超过库存
+					if (this.count > this.skuSelect.skuStock) {
+						return uni.$u.toast(`该规格仅剩${this.skuSelect.skuStock}件,请调整数量`);
+					}
+					if (!this.isLogin) {
+						uni.navigateTo({
+							url: '/packageUser/pages/login/index'
+						});
+						return;
+					}
+					productCartSave({
+						skuId: this.skuSelect.id,
+						count: this.count
+					}).then((res) => {
+						if (res.code == 200) {
+							this.startAddCartAnim();
+							this.$refs.popup.close();
+							this.getCartCount();
+							uni.$u.toast("加入购物车成功");
+						}
+					})
+				}
+			},
+			goScore() {
+				this.$route('/packageOrder/pages/score/product-score?productId=' + this.params.productId)
+			},
+			skuPopUp(buyType) {
+				this.$refs.popup.open("bottom")
+				this.buyType = buyType;
+				this.skuSelect = this.detail.sku[0]
+				this.selectId = this.skuSelect.id
+			},
+			onceOrder(skuId) {
+				console.log(skuId, "skuid")
+				this.$refs.popup.open("bottom");
+				this.buyType = "buy";
+				const matchedSku = this.detail.sku.find(item => item.id === skuId);
+				if (matchedSku) {
+					this.skuSelect = matchedSku;
+					this.selectId = this.skuSelect.id;
+				} else {
+					uni.$u.toast("该规格已下架")
+				}
+			},
+			getScoreList() {
+				scoreList(this.params).then((res) => {
+					this.scoreList = res.data.records
+					this.scoreList.forEach(item => {
+						item.imgList = item.images.split(",")
+					})
+				})
+			},
+			async getDetail(id) {
+				uni.showLoading({
+					title: '加载中...',
+					mask: true
+				});
+				try {
+					const res = await productDetail({
+						id
+					})
+					if (res.code === 200) {
+						this.detail = res.data
+						this.skuNum = this.detail.sku.length
+						this.imageList = res.data.carouselImg.split(",");
+						this.isCollect = res.data.isCollect
+					} else if (res.code === 401) {
+						uni.navigateTo({
+							url: '/packageUser/pages/login/index'
+						});
+					}
+				} catch (e) {
+					console.error('获取商品详情失败:', e);
+					if (e.code === 401) {
+						uni.navigateTo({
+							url: '/packageUser/pages/login/index'
+						});
+					} else {
+						uni.$u.toast('获取商品详情失败');
+					}
+				}
+				uni.hideLoading()
+			},
+			navigationTo(routerName) {
+				if (routerName) {
+					this.$route(routerName);
+				}
+			},
+			async handlerCollect() {
+				if (this.isCollect) {
+					try {
+						await productClearCollect({
+							productId: this.params.productId
+						})
+						this.$u.toast('取消收藏成功!')
+						this.isCollect = !this.isCollect
+					} catch (e) {
+						this.$u.toast('取消收藏失败!')
+					}
+				} else {
+					try {
+						await productAddCollect({
+							productId: this.params.productId
+						})
+						this.$u.toast('收藏成功!')
+						this.isCollect = !this.isCollect
+					} catch (e) {
+						this.$u.toast('收藏失败!')
+					}
+				}
+			},
+			addCart() {
+				// 检查商品总库存
+				if (!this.detail.stock || this.detail.stock <= 0) {
+					return uni.$u.toast('商品库存不足,暂不能加入购物车');
+				}
+				this.skuPopUp('cart')
+				this.popupText = "加入购物车"
+			},
+			goPay() {
+				// 检查商品总库存
+				if (!this.detail.stock || this.detail.stock <= 0) {
+					return uni.$u.toast('商品库存不足,暂不能购买');
+				}
+				this.skuPopUp('buy')
+				this.popupText = "立即购买"
+			},
+			sectionChange(index) {
+				this.curNow = index;
+			},
+			selectRadio(item, index) {
+				this.selectId = item.id;
+				this.skuSelect = this.detail.sku[index]
+			},
+			// 预览图片
+			previewImage(imageSrc) {
+				uni.previewImage({
+					urls: [imageSrc],
+					current: imageSrc
+				});
+			},
+			startAddCartAnim() {
+				const query = uni.createSelectorQuery();
+				query.select('.prpupImg').boundingClientRect();
+				query.select('.cart-icon-wrapper').boundingClientRect();
+				query.exec(([productRect, cartRect]) => {
+					if (productRect && cartRect) {
+						const startX = productRect.left + productRect.width / 2;
+						const startY = productRect.top + productRect.height / 2;
+
+						this.ballStyle = {
+							left: startX + 'px',
+							top: startY + 'px'
+						}
+
+						this.ballShow = true;
+
+						setTimeout(() => {
+							this.ballStyle = {
+								left: cartRect.left + cartRect.width / 2 + 'px',
+								top: cartRect.top + cartRect.height / 2 + 'px'
+							}
+
+							setTimeout(() => {
+								this.ballShow = false;
+							}, 450);
+						}, 50);
+					}
+				});
+			},
+		}
+	};
+</script>
+
+<style lang="scss">
+	.popup {
+		position: relative;
+		padding: 30rpx;
+		padding-bottom: 120rpx;
+		/* 给底部按钮留出空间 */
+		background-color: #fff;
+	}
+
+	.prpupImg {
+		border-radius: 20rpx;
+		width: 220rpx;
+		height: 220rpx;
+		cursor: pointer;
+		transition: transform 0.2s ease;
+	}
+
+	.prpupImg:active {
+		transform: scale(0.95);
+	}
+
+	.guige {
+		background-color: #F5F5F5;
+		padding: 10rpx;
+		border-radius: 15rpx;
+	}
+
+	.skuImg {
+		width: 60rpx;
+		height: 60rpx;
+		margin-right: 30rpx;
+	}
+
+	.productStats {
+		margin: 25rpx 15rpx 25rpx 15rpx;
+		border-radius: 20rpx;
+		padding: 32rpx;
+		background-color: #FFFFFF;
+	}
+
+	.vertical-line {
+		margin-left: 20rpx;
+		margin-right: 20rpx;
+		width: 1px;
+		height: 20rpx;
+		background-color: #a8a8a8;
+	}
+
+	.productInfo {
+		background-color: white;
+		padding: 20rpx;
+		margin: 25rpx 15rpx 25rpx 15rpx;
+		border-radius: 20rpx;
+	}
+
+	/* 新的商品信息样式 */
+	.product-info {
+		background-color: #ffffff;
+		padding: 32rpx 24rpx;
+		margin: 24rpx 16rpx;
+		border-radius: 24rpx;
+		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
+		display: flex;
+		align-items: flex-start;
+		justify-content: space-between;
+	}
+
+	.product-main {
+		flex: 1;
+		margin-right: 24rpx;
+	}
+
+	.price-section {
+		display: flex;
+		align-items: baseline;
+		margin-bottom: 16rpx;
+	}
+
+	.current-price {
+		font-size: 48rpx;
+		font-weight: bold;
+		color: #ff4757;
+		margin-right: 8rpx;
+	}
+
+	.price-suffix {
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #ff4757;
+		margin-right: 16rpx;
+	}
+
+	.original-price {
+		font-size: 28rpx;
+		color: #999999;
+		text-decoration: line-through;
+	}
+
+	.product-name {
+		font-size: 34rpx;
+		font-weight: bold;
+		color: #333333;
+		line-height: 1.4;
+		margin-bottom: 16rpx;
+		display: -webkit-box;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 2;
+		overflow: hidden;
+	}
+
+	.product-stats {
+		display: flex;
+		align-items: center;
+	}
+
+	.stat-item {
+		font-size: 25rpx;
+		color: #666666;
+	}
+
+	.divider {
+		width: 2rpx;
+		height: 24rpx;
+		background-color: #e0e0e0;
+		margin: 0 16rpx;
+	}
+
+	.share-section {
+		flex-shrink: 0;
+	}
+
+	.share-btn {
+		background: #ffffff;
+		border: 2rpx solid #f0f0f0;
+		border-radius: 20rpx;
+		padding: 0;
+		transition: all 0.25s ease;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 80rpx;
+		height: 80rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+		position: relative;
+	}
+
+	.share-btn::after {
+		content: '';
+		position: absolute;
+		top: -2rpx;
+		left: -2rpx;
+		right: -2rpx;
+		bottom: -2rpx;
+		background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #f5576c);
+		border-radius: 20rpx;
+		z-index: -1;
+		opacity: 0;
+		transition: opacity 0.25s ease;
+	}
+
+	.share-btn:active::after {
+		opacity: 1;
+	}
+
+	.share-btn:active {
+		transform: scale(0.92);
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
+		border-color: transparent;
+	}
+
+	.share-btn .u-icon {
+		color: #666666;
+		transition: all 0.25s ease;
+		font-size: 24rpx;
+	}
+
+	.share-btn:active .u-icon {
+		color: #ffffff;
+		transform: rotate(15deg) scale(1.1);
+	}
+
+	.swiper {
+		width: 100%;
+		position: relative;
+	}
+
+	.navigation {
+		position: fixed;
+		bottom: 0;
+		width: 100%;
+		border: solid 2rpx #f2f2f2;
+		background-color: #ffffff;
+		padding: 16rpx 0;
+
+		.flex {
+			display: flex;
+			flex-direction: row;
+
+			.left {
+				width: 40%;
+				display: flex;
+				font-size: 20rpx;
+				justify-content: space-around;
+
+				.item {
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					align-items: center;
+					flex: 1;
+
+					.tabbar-slot-icon {
+						width: 44rpx;
+						height: 44rpx;
+						margin: 0 auto;
+					}
+				}
+			}
+
+			.right {
+				width: 60%;
+				display: flex;
+				font-size: 28rpx;
+				align-items: center;
+
+				.cartBtn {
+					width: 210rpx;
+					height: 72rpx;
+					font-size: 28rpx;
+					text-align: center;
+					line-height: 72rpx;
+					border-radius: 36rpx 0rpx 0rpx 36rpx;
+					color: #ffffff;
+				}
+
+				.buyBtn {
+					font-size: 28rpx;
+					width: 210rpx;
+					height: 72rpx;
+					text-align: center;
+					line-height: 72rpx;
+					border-radius: 0rpx 36rpx 36rpx 0rpx;
+					color: #ffffff;
+				}
+
+				.buy {
+					background: #F95B5B
+				}
+
+				.cart {
+					background: #F9AE5B
+				}
+
+				.disabled {
+					background: #cccccc !important;
+					color: #999999 !important;
+					cursor: not-allowed;
+				}
+			}
+		}
+	}
+
+	.gray-line {
+		height: 1px;
+		background-color: #f9f9f9;
+		width: 100%;
+	}
+
+	.flex-container {
+		display: flex;
+		flex-wrap: wrap;
+		margin-bottom: 30rpx;
+	}
+
+	.hotBg {
+		margin-bottom: 10rpx;
+		margin-right: 30rpx;
+		padding: 10px;
+		font-size: 28rpx;
+		border-radius: 5px;
+	}
+
+	.selected {
+		background-color: orange;
+	}
+
+	.fixed-button {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		padding: 15rpx;
+		background-color: #F95B5B;
+		color: white;
+		border: none;
+	}
+
+	.avatar {
+		width: 70rpx;
+		height: 70rpx;
+		border-radius: 50%;
+		overflow: hidden;
+	}
+
+	.scoreImg {
+		width: 150rpx;
+		height: 150rpx;
+	}
+
+	.cart-icon-wrapper {
+		position: relative;
+		display: inline-block;
+	}
+
+	.cart-badge {
+		position: absolute;
+		top: -10rpx;
+		right: -10rpx;
+		background-color: #FF4B4B;
+		color: #fff;
+		font-size: 20rpx;
+		padding: 2rpx 8rpx;
+		border-radius: 20rpx;
+		min-width: 28rpx;
+		height: 28rpx;
+		text-align: center;
+		line-height: 28rpx;
+	}
+
+	.cart-ball {
+		position: fixed;
+		z-index: 999;
+		pointer-events: none;
+		transition: all 0.45s cubic-bezier(0.49, -0.29, 0.75, 0.41);
+
+		.inner {
+			width: 16px;
+			height: 16px;
+			border-radius: 50%;
+			background: #ff6b81;
+			transition: all 0.45s linear;
+		}
+	}
+
+	.cart-ball.active .inner {
+		animation: ball-circular 0.45s linear;
+	}
+
+	@keyframes ball-circular {
+		0% {
+			transform: scale(1);
+		}
+
+		50% {
+			transform: scale(0.8);
+		}
+
+		100% {
+			transform: scale(0.5);
+		}
+	}
+
+	.custom-share-btn {
+		background: #ffffff;
+		border: 2rpx solid #e3e3e3;
+		border-radius: 20rpx;
+		padding: 0;
+		transition: all 0.25s ease;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 80rpx;
+		height: 80rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+		position: relative;
+	}
+
+	.custom-share-btn::after {
+		content: '';
+		position: absolute;
+		top: -2rpx;
+		left: -2rpx;
+		right: -2rpx;
+		bottom: -2rpx;
+		background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #f5576c);
+		border-radius: 20rpx;
+		z-index: -1;
+		opacity: 0;
+		transition: opacity 0.25s ease;
+	}
+
+	.custom-share-btn:active::after {
+		opacity: 1;
+	}
+
+	.custom-share-btn:active {
+		transform: scale(0.92);
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
+		border-color: transparent;
+	}
+
+	.custom-share-btn .u-icon {
+		color: #666666;
+		transition: all 0.25s ease;
+		font-size: 24rpx;
+	}
+
+	.custom-share-btn:active .u-icon {
+		color: #ffffff;
+		transform: rotate(15deg) scale(1.1);
+	}
+</style>

+ 224 - 0
packageShop/pages/score/index.vue

@@ -0,0 +1,224 @@
+<template>
+  <view class="score-container">
+    <view class="product-info">
+      <image :src="product.image" mode="aspectFill" class="product-image"></image>
+      <view class="product-detail">
+        <view class="product-name">{{product.name}}</view>
+        <view class="product-price">¥{{product.price}}</view>
+      </view>
+    </view>
+
+    <view class="score-form">
+      <view class="score-item">
+        <view class="label">商品评分</view>
+        <u-rate v-model="form.score" count="5" activeColor="#D93025"></u-rate>
+      </view>
+
+      <view class="content-item">
+        <u-textarea
+          v-model="form.content"
+          placeholder="请输入评价内容,分享您的使用体验"
+          height="200"
+          count
+          maxlength="500">
+        </u-textarea>
+      </view>
+
+      <view class="upload-item">
+        <view class="label">上传图片</view>
+        <u-upload
+          :fileList="form.images"
+          @afterRead="afterRead"
+          @delete="deletePic"
+          name="1"
+          multiple
+          maxCount="9">
+        </u-upload>
+      </view>
+
+      <view class="anonymous-item">
+        <text>匿名评价</text>
+        <u-switch v-model="form.isAnonymous" activeColor="#D93025"></u-switch>
+      </view>
+    </view>
+
+    <view class="submit-btn">
+      <u-button type="primary" @click="submit" color="#D93025">提交评价</u-button>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      orderId: '',
+      productId: '',
+      product: {
+        image: '',
+        name: '',
+        price: ''
+      },
+      form: {
+        score: 5,
+        content: '',
+        images: [],
+        isAnonymous: false
+      }
+    }
+  },
+  onLoad(options) {
+    this.orderId = options.orderId
+    this.productId = options.productId
+    this.getProductInfo()
+  },
+  methods: {
+    async getProductInfo() {
+      try {
+        const res = await this.$api.product.detail(this.productId)
+        this.product = res.data
+      } catch (e) {
+        this.$u.toast('获取商品信息失败')
+      }
+    },
+    async afterRead(event) {
+      const { file } = event
+      // 当设置 multiple 为 true 时, file 为数组格式
+      const uploadPromises = (Array.isArray(file) ? file : [file]).map(item => {
+        return this.uploadFilePromise(item)
+      })
+
+      try {
+        const urls = await Promise.all(uploadPromises)
+        this.form.images = [...this.form.images, ...urls]
+      } catch (e) {
+        this.$u.toast('上传图片失败')
+      }
+    },
+    uploadFilePromise(file) {
+      return new Promise((resolve, reject) => {
+        uni.uploadFile({
+          url: this.$api.common.uploadUrl,
+          filePath: file.url,
+          name: 'file',
+          success: (res) => {
+            const data = JSON.parse(res.data)
+            resolve(data.url)
+          },
+          fail: reject
+        })
+      })
+    },
+    deletePic(event) {
+      const index = event.index
+      this.form.images.splice(index, 1)
+    },
+    async submit() {
+      if (!this.form.content.trim()) {
+        return this.$u.toast('请输入评价内容')
+      }
+
+      try {
+        await this.$api.order.submitScore({
+          orderId: this.orderId,
+          productId: this.productId,
+          ...this.form
+        })
+        this.$u.toast('评价成功')
+        setTimeout(() => {
+          uni.navigateBack()
+        }, 1500)
+      } catch (e) {
+        this.$u.toast('评价失败')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.score-container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 120rpx;
+
+  .product-info {
+    background: #fff;
+    padding: 20rpx;
+    display: flex;
+    align-items: center;
+    margin-bottom: 20rpx;
+
+    .product-image {
+      width: 160rpx;
+      height: 160rpx;
+      border-radius: 8rpx;
+      margin-right: 20rpx;
+    }
+
+    .product-detail {
+      flex: 1;
+
+      .product-name {
+        font-size: 28rpx;
+        color: #333;
+        margin-bottom: 10rpx;
+      }
+
+      .product-price {
+        font-size: 32rpx;
+        color: #D93025;
+        font-weight: bold;
+      }
+    }
+  }
+
+  .score-form {
+    background: #fff;
+    padding: 20rpx;
+
+    .score-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 30rpx;
+
+      .label {
+        font-size: 28rpx;
+        color: #333;
+        margin-right: 20rpx;
+      }
+    }
+
+    .content-item {
+      margin-bottom: 30rpx;
+    }
+
+    .upload-item {
+      margin-bottom: 30rpx;
+
+      .label {
+        font-size: 28rpx;
+        color: #333;
+        margin-bottom: 20rpx;
+      }
+    }
+
+    .anonymous-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 28rpx;
+      color: #333;
+    }
+  }
+
+  .submit-btn {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 20rpx 40rpx;
+    background: #fff;
+  }
+}
+</style> 

+ 166 - 0
packageShop/pages/search/index.vue

@@ -0,0 +1,166 @@
+<template>
+	<view>
+		<view class="flex-items mar-t-20">
+			<view class="mar-r-20">
+				<!-- <uni-icons @tap="back" type="left" color="#545454" size="30"></uni-icons> -->
+			</view>
+			<view class="search-box mar-r-20">
+				<input confirm-type="search" class="search-input" focus type="text" placeholder="请输入搜索内容"
+					v-model="historySearch.name" />
+				<uni-icons @tap="clear" v-if="historySearch.name" type="closeempty" color="#545454" size="20"
+					class="mar-r-20"></uni-icons>
+				<button class="search-button" @click="onSearchClick">搜索</button>
+			</view>
+		</view>
+		<view class="flex-items-between pad-32">
+			<view class="font28 font-bold">
+				历史搜索
+			</view>
+			<uni-icons @tap="delSearch" type="trash" size="26"></uni-icons>
+		</view>
+		<view class="pad-lr-32">
+			<view class="search-container">
+				<view v-for="(item, index) in historySearchList" :key="index" class="search-item">
+					<view class="searchBg mar-b-16" @tap="onHistorySearch(item.name)">
+						{{ item.name }}
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		delHistorySearch,
+		submitHistorySearch,
+		getHistorySearchList
+	} from '@/config/api.js';
+	import {
+		MAIN_COLOR
+	} from '@/common/config.js'
+
+	export default {
+		data() {
+			return {
+				bgColor: 'transparent',
+				statusBarHeight: 0, // 状态栏高度
+				historySearch: {
+					name: "",
+					type: ""
+				},
+				historySearchList: [],
+			}
+		},
+		onLoad(op) {
+			console.log(op.searchKey)
+			this.historySearch.type = op.type
+			if (op.searchKey) {
+				console.log(op.searchKey)
+				this.historySearch.name = op.searchKey
+			}
+			// 获取设备信息,包括状态栏高度
+			const systemInfo = uni.getSystemInfoSync();
+			this.statusBarHeight = systemInfo.statusBarHeight;
+			this.getSearch();
+		},
+		methods: {
+			clear() {
+				this.historySearch.name = ""
+			},
+			onHistorySearch(name) {
+				this.historySearch.name = name
+				if (this.historySearch.type == 2) {
+					this.$route('/packageOrder/pages/list/index?serachKey=' + this.historySearch.name)
+				}
+				if (this.historySearch.type == 1) {
+					this.$route('/packageShop/pages/search/product-list?serachKey=' + this.historySearch.name)
+				}
+			},
+			back() {
+				uni.navigateBack({
+					delta: 1
+				})
+			},
+			getSearch() {
+				getHistorySearchList({
+					type: this.historySearch.type
+				}).then((res) => {
+					this.historySearchList = res.data
+				})
+			},
+			delSearch() {
+				delHistorySearch().then((res) => {
+					this.getSearch()
+				})
+			},
+			onSearchClick() {
+				console.log(this.historySearch.type)
+				// 执行搜索操作
+				submitHistorySearch(this.historySearch)
+				if (this.historySearch.type == 2) {
+					this.$route('/packageOrder/pages/list/index?serachKey=' + this.historySearch.name)
+				}
+				if (this.historySearch.type == 1) {
+					this.$route('/packageShop/pages/search/product-list?serachKey=' + this.historySearch.name)
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	.search-container {
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.search-item {
+		margin-right: 10rpx;
+		margin-bottom: 10rpx;
+	}
+
+	.searchBg {
+		background-color: #d8d8d8;
+		padding: 8rpx 12rpx;
+		border-radius: 10rpx;
+		font-size: 14px;
+		color: #333;
+	}
+
+	.search-box {
+		display: flex;
+		align-items: center;
+		border-radius: 20rpx;
+		background-color: #f0f0f0;
+		padding: 10rpx 20rpx;
+		width: 100%;
+		/* Ensure it takes full width */
+	}
+
+	.search-input {
+		flex: 1;
+		border: none;
+		outline: none;
+		font-size: 14px;
+		color: #999;
+		background-color: #f0f0f0;
+		border-radius: 20rpx;
+		padding: 8rpx 12rpx;
+		width: 100%;
+	}
+
+	.search-button {
+		background-color: #f27c22;
+		/* 橘色背景 */
+		color: white;
+		border: none;
+		padding: 0rpx 30rpx 0rpx 30rpx;
+		border-radius: 20rpx;
+		margin-left: 10rpx;
+		font-size: 14px;
+		cursor: pointer;
+		white-space: nowrap;
+		/* Prevent text overflow */
+	}
+</style>

+ 917 - 0
packageShop/pages/search/product-list.vue

@@ -0,0 +1,917 @@
+<template>
+	<view class="container">
+		<view class="page-container">
+
+
+			<!-- 主要内容区域 -->
+			<view class="main-content">
+
+				<!-- 右侧商品列表 -->
+				<view class="product-list">
+					<!-- 搜索框 -->
+					<view class="search-section">
+						<view class="search-bar">
+							<view class="search-icon-wrapper">
+								<image class="search-icon" src="/static/images/search.png"></image>
+							</view>
+							<input class="search-input" v-model="params.name" placeholder="搜索唐卡"
+								@confirm="onSearchConfirm" />
+							<view class="search-btn" @click="onSearchConfirm">
+								<text class="search-btn-text">搜索</text>
+							</view>
+						</view>
+					</view>
+
+					<!-- 筛选栏 -->
+					<view class="filter-bar">
+						<view class="filter-item" :class="{ 'active': activeFilter === 'all' }"
+							@click="switchFilter('all')">
+							<text class="filter-text">全部商品</text>
+						</view>
+						<view class="filter-item" :class="{ 'active': activeFilter.includes('price') }"
+							@click="toggleSort('price')">
+							<text class="filter-text">价格排序</text>
+							<view class="arrow-container">
+								<text class="arrow up" :class="{ 'active': activeFilter === 'price_asc' }">▲</text>
+								<text class="arrow down" :class="{ 'active': activeFilter === 'price_desc' }">▼</text>
+							</view>
+						</view>
+						<view class="filter-item" :class="{ 'active': activeFilter.includes('sales') }"
+							@click="toggleSort('sales')">
+							<text class="filter-text">销量排序</text>
+							<view class="arrow-container">
+								<text class="arrow up" :class="{ 'active': activeFilter === 'sales_asc' }">▲</text>
+								<text class="arrow down" :class="{ 'active': activeFilter === 'sales_desc' }">▼</text>
+							</view>
+						</view>
+					</view>
+
+					<scroll-view :scroll-top="scrollTop" scroll-y="true" class="scroll-Y" @scrolltolower="lower">
+						<view v-if="goodsList.length > 0" class="goods-container">
+							<view class="goods-grid">
+								<view v-for="(product, index) in goodsList" :key="index" class="product-card"
+									@click="detail(product.id)">
+									<view class="product-image-container">
+										<image class="product-image" :src="product.images" mode="aspectFill"></image>
+										<view class="product-badge" v-if="product.salesTotal > 100">热销</view>
+									</view>
+									<view class="product-content">
+										<view class="product-name">{{ product.name }}</view>
+										<view class="product-meta">
+											<view class="meta-info">
+												<view class="rating" v-if="product.productAvg">
+													<text class="rating-star">★</text>
+													<text class="rating-value">{{ product.productAvg }}</text>
+												</view>
+											</view>
+											<view class="sales-info">
+												<text class="sales-count">已售 {{ product.salesTotal }} 件</text>
+											</view>
+										</view>
+										<view class="product-footer">
+											<view class="price-section">
+												<text class="price-symbol">¥</text>
+												<text class="price-value">{{ product.price }}</text>
+												<text class="original-price"
+													v-if="product.originalPrice">¥{{ product.originalPrice }}</text>
+											</view>
+											<view class="buy-btn" @click.stop="detail(product.id)">
+												<text class="buy-text">购买</text>
+											</view>
+										</view>
+									</view>
+								</view>
+							</view>
+						</view>
+						<view v-else class="empty-state">
+							<view class="empty-icon">☸</view>
+							<text class="empty-text">暂无商品</text>
+							<text class="empty-subtext">敬请期待更多精美唐卡</text>
+						</view>
+						<!-- 加载状态 -->
+						<view v-if="isLoading" class="loading-tip">
+							<view class="loading-icon">☸</view>
+							<text class="loading-text">加载中...</text>
+						</view>
+						<view v-if="!isLoading && noMoreData" class="no-more-tip">
+							<view class="no-more-decoration">
+								<view class="decoration-line"></view>
+								<text class="decoration-text">—— 已加载全部商品 ——</text>
+								<view class="decoration-line"></view>
+							</view>
+						</view>
+					</scroll-view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		productList,
+	} from '@/config/api.js';
+	export default {
+		data() {
+			return {
+				scrollTop: 0,
+				goodsList: [],
+				isLoading: false, // 是否正在加载
+				noMoreData: false, // 是否没有更多数据
+				params: {
+					current: 1,
+					size: 10,
+					categoriesId: "",
+					name: "",
+					sort: "",
+					arrow: "",
+				}, //请求参数
+				activeFilter: 'all'
+			};
+		},
+
+		onLoad(op) {
+			this.params.name = op.serachKey
+			console.log(op, "name")
+			this.init()
+		},
+		methods: {
+			detail(id) {
+				uni.navigateTo({
+					url: '/packageShop/pages/detail/index?id=' + id
+				});
+			},
+			lower() {
+				if (!this.isLoading && !this.noMoreData) {
+					this.loadGoods();
+				}
+			},
+			async loadGoods() {
+				this.isLoading = true;
+				try {
+					// 搜索页面不需要分类ID,直接使用搜索关键词
+					const response = await productList(this.params);
+					uni.hideLoading();
+
+					// 过滤并处理商品数据
+					const newGoods = response.data.records.filter(item => item.shelfLife === 1).map(item => ({
+						...item,
+						productAvg: item.productAvg || 5.0
+					}));
+
+					if (newGoods.length === 0) {
+						this.noMoreData = true;
+					} else {
+						this.goodsList = this.params.current === 1 ? newGoods : [...this.goodsList, ...newGoods];
+						this.params.current++;
+					}
+				} catch (error) {
+					console.error('加载商品数据失败:', error);
+					uni.showToast({
+						title: '加载失败,请重试',
+						icon: 'none'
+					});
+				} finally {
+					this.isLoading = false;
+				}
+			},
+			init() {
+				// 直接加载商品,不需要分类
+				this.loadGoods();
+			},
+
+
+
+			switchFilter(filter) {
+				this.activeFilter = filter;
+				this.resetParams()
+				this.loadGoods()
+			},
+			resetParams() {
+				this.params.arrow = ""
+				this.params.current = 1
+				this.params.sort = ""
+				this.goodsList = []
+				// 保留搜索关键词,不清空 params.name
+			},
+			toggleSort(filter) {
+				this.resetParams()
+				if (this.activeFilter === `${filter}_asc`) {
+					if (filter == "price") {
+						this.params.sort = 1
+					} else {
+						this.params.sort = 2
+					}
+					this.params.arrow = 2
+					this.activeFilter = `${filter}_desc`;
+				} else {
+					this.activeFilter = `${filter}_asc`;
+					if (filter == "price") {
+						this.params.sort = 1
+					} else {
+						this.params.sort = 2
+					}
+					this.params.arrow = 1
+				}
+				this.loadGoods()
+			},
+
+			onSearchConfirm() {
+				// 确认搜索时重新加载商品
+				this.resetParams();
+				this.loadGoods();
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		min-height: 100vh;
+		background: linear-gradient(135deg, #F5E6D3 0%, #E2D1C3 100%);
+		position: relative;
+	}
+
+	.container::before {
+		content: '';
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="tibetan" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M0 0h20v20H0z" fill="none"/><path d="M10 2a2 2 0 110 4 2 2 0 010-4zm8 8a2 2 0 110 4 2 2 0 010-4zm-16 0a2 2 0 110 4 2 2 0 010-4zm8 8a2 2 0 110 4 2 2 0 010-4z" fill="%23A67C52" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23tibetan)"/></svg>');
+		opacity: 0.15;
+		pointer-events: none;
+		z-index: 0;
+	}
+
+	.page-container {
+		position: relative;
+		z-index: 1;
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+	}
+
+	/* 页面标题 */
+	.page-header {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		padding: 40rpx 30rpx 30rpx;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.page-header::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="endless-knot" width="50" height="50" patternUnits="userSpaceOnUse"><path d="M25 10 L40 10 L40 40 L10 40 L10 25 L25 25 Z" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="2"/></pattern></defs><rect width="100" height="100" fill="url(%23endless-knot)"/></svg>');
+		opacity: 0.2;
+	}
+
+	.header-content {
+		position: relative;
+		z-index: 1;
+		text-align: center;
+	}
+
+	.title-section {
+		margin-bottom: 16rpx;
+	}
+
+	.page-title {
+		font-size: 48rpx;
+		font-weight: 700;
+		color: #FFD700;
+		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
+		letter-spacing: 4rpx;
+	}
+
+	.title-decoration {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-top: 16rpx;
+		gap: 20rpx;
+	}
+
+	.decoration-line {
+		width: 60rpx;
+		height: 2rpx;
+		background: linear-gradient(90deg, #FFD700, #FFA500);
+	}
+
+	.decoration-symbol {
+		color: #FFD700;
+		font-size: 28rpx;
+		opacity: 0.8;
+	}
+
+	.subtitle {
+		font-size: 26rpx;
+		color: rgba(255, 215, 0, 0.8);
+		font-weight: 300;
+		letter-spacing: 1rpx;
+	}
+
+	/* 主要内容区域 */
+	.main-content {
+		flex: 1;
+		display: flex;
+		background: rgba(255, 248, 231, 0.3);
+	}
+
+	/* 左侧分类栏 */
+	.category-list {
+		width: 200rpx;
+		background: rgba(255, 248, 231, 0.95);
+		border-right: 2rpx solid rgba(139, 69, 19, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.category-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 30rpx 20rpx;
+		position: relative;
+		transition: all 0.3s ease;
+		border-bottom: 1rpx solid rgba(139, 69, 19, 0.05);
+
+		&:active {
+			transform: scale(0.95);
+		}
+
+		&.active {
+			background: linear-gradient(135deg, rgba(139, 69, 19, 0.1) 0%, rgba(101, 67, 33, 0.1) 100%);
+			border-right: 4rpx solid #8B4513;
+
+			.category-name {
+				color: #8B4513;
+				font-weight: 600;
+			}
+
+			.category-icon {
+				background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+				border-color: #FFD700;
+				box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+			}
+
+			.category-image {
+				transform: scale(1.1);
+				filter: brightness(1.1) contrast(1.1);
+			}
+
+			.icon-symbol {
+				color: #FFD700;
+			}
+		}
+	}
+
+	.category-icon {
+		width: 80rpx;
+		height: 80rpx;
+		background: rgba(139, 69, 19, 0.1);
+		border: 2rpx solid rgba(139, 69, 19, 0.2);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 16rpx;
+		transition: all 0.3s ease;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.category-image {
+		width: 100%;
+		height: 100%;
+		border-radius: 50%;
+		transition: all 0.3s ease;
+	}
+
+	.icon-symbol {
+		font-size: 32rpx;
+		color: #8B4513;
+		transition: all 0.3s ease;
+	}
+
+	.category-name {
+		font-size: 26rpx;
+		color: #A67C52;
+		font-weight: 500;
+		text-align: center;
+		line-height: 1.3;
+		transition: all 0.3s ease;
+	}
+
+	.active-indicator {
+		position: absolute;
+		right: 0;
+		top: 50%;
+		transform: translateY(-50%);
+		width: 6rpx;
+		height: 40rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border-radius: 4rpx 0 0 4rpx;
+		box-shadow: 0 0 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.category-glow {
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: linear-gradient(135deg, rgba(255, 215, 0, 0.1) 0%, rgba(255, 165, 0, 0.1) 100%);
+		border-radius: 12rpx;
+		pointer-events: none;
+		animation: glowPulse 2s ease-in-out infinite;
+	}
+
+	@keyframes glowPulse {
+		0% {
+			opacity: 0.3;
+			transform: scale(1);
+		}
+
+		50% {
+			opacity: 0.6;
+			transform: scale(1.02);
+		}
+
+		100% {
+			opacity: 0.3;
+			transform: scale(1);
+		}
+	}
+
+	/* 右侧商品列表 */
+	.product-list {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		background: rgba(255, 248, 231, 0.2);
+	}
+
+	/* 搜索框 */
+	.search-section {
+		padding: 24rpx 20rpx;
+		background: rgba(255, 248, 231, 0.95);
+		border-bottom: 2rpx solid rgba(139, 69, 19, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.search-bar {
+		display: flex;
+		align-items: center;
+		background: rgba(255, 248, 231, 0.95);
+		border: 2rpx solid rgba(139, 69, 19, 0.2);
+		border-radius: 32rpx;
+		padding: 16rpx 24rpx;
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.15);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+	}
+
+	.search-bar::before {
+		content: '';
+		position: absolute;
+		top: 2rpx;
+		left: 2rpx;
+		right: 2rpx;
+		bottom: 2rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 30rpx;
+		pointer-events: none;
+	}
+
+	// .search-bar:active {
+	// 	transform: scale(0.98);
+	// 	box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.2);
+	// }
+
+	.search-icon-wrapper {
+		width: 32rpx;
+		height: 32rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 12rpx;
+	}
+
+	.search-icon {
+		width: 28rpx;
+		height: 28rpx;
+		opacity: 0.6;
+	}
+
+	.search-input {
+		font-size: 26rpx;
+		color: #8B4513;
+		flex: 1;
+		background: transparent;
+		border: none;
+		outline: none;
+	}
+
+	.search-input::placeholder {
+		color: #A67C52;
+		opacity: 0.8;
+	}
+
+	.search-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border-radius: 24rpx;
+		padding: 12rpx 20rpx;
+		margin-left: 12rpx;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+		cursor: pointer;
+	}
+
+	.search-btn:active {
+		transform: scale(0.95);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.search-btn-text {
+		color: #FFD700;
+		font-size: 24rpx;
+		font-weight: 600;
+		letter-spacing: 2rpx;
+	}
+
+	/* 筛选栏 */
+	.filter-bar {
+		padding: 24rpx 20rpx;
+		background: rgba(255, 248, 231, 0.95);
+		display: flex;
+		justify-content: space-around;
+		border-bottom: 2rpx solid rgba(139, 69, 19, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.filter-item {
+		display: flex;
+		align-items: center;
+		padding: 12rpx 20rpx;
+		border-radius: 20rpx;
+		transition: all 0.3s ease;
+		position: relative;
+
+		&:active {
+			transform: scale(0.95);
+		}
+
+		&.active {
+			background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+			box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+
+			.filter-text {
+				color: #FFD700;
+				font-weight: 600;
+			}
+
+			.arrow {
+				color: #FFD700;
+			}
+		}
+	}
+
+	.filter-text {
+		font-size: 26rpx;
+		color: #8B4513;
+		font-weight: 500;
+		transition: all 0.3s ease;
+	}
+
+	.arrow-container {
+		display: flex;
+		flex-direction: column;
+		margin-left: 8rpx;
+	}
+
+	.arrow {
+		font-size: 18rpx;
+		line-height: 1;
+		color: #A67C52;
+		transition: all 0.3s ease;
+
+		&.active {
+			color: #FFD700;
+		}
+	}
+
+	/* 商品列表 */
+	.scroll-Y {
+		flex: 1;
+	}
+
+	.goods-container {
+		padding: 20rpx;
+	}
+
+	.goods-grid {
+		display: grid;
+		grid-template-columns: 1fr 1fr;
+		gap: 20rpx;
+	}
+
+	.product-card {
+		background: rgba(255, 248, 231, 0.95);
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 20rpx;
+		overflow: hidden;
+		box-shadow: 0 8rpx 32rpx rgba(139, 69, 19, 0.08);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+		width: 100%;
+	}
+
+	.product-card::before {
+		content: '';
+		position: absolute;
+		top: 10rpx;
+		left: 10rpx;
+		right: 10rpx;
+		bottom: 10rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 16rpx;
+		pointer-events: none;
+		z-index: 1;
+	}
+
+	.product-card:active {
+		transform: translateY(-4rpx);
+		box-shadow: 0 12rpx 40rpx rgba(139, 69, 19, 0.12);
+	}
+
+	.product-image-container {
+		position: relative;
+		width: 100%;
+		height: 300rpx;
+		overflow: hidden;
+	}
+
+	.product-image {
+		width: 100%;
+		height: 100%;
+		transition: transform 0.3s ease;
+	}
+
+	.product-card:active .product-image {
+		transform: scale(1.05);
+	}
+
+	.product-badge {
+		position: absolute;
+		top: 16rpx;
+		right: 16rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border: 1rpx solid #FFD700;
+		color: #FFD700;
+		font-size: 22rpx;
+		padding: 6rpx 12rpx;
+		border-radius: 12rpx;
+		font-weight: 500;
+		z-index: 2;
+	}
+
+	.product-content {
+		padding: 16rpx;
+		position: relative;
+		z-index: 2;
+	}
+
+	.product-name {
+		font-size: 26rpx;
+		color: #8B4513;
+		font-weight: 600;
+		line-height: 1.3;
+		margin-bottom: 8rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+	}
+
+	.product-desc {
+		font-size: 24rpx;
+		color: #A67C52;
+		margin-bottom: 16rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+		line-height: 1.4;
+	}
+
+	.product-meta {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 12rpx;
+	}
+
+	.meta-info {
+		display: flex;
+		align-items: center;
+		gap: 16rpx;
+	}
+
+	.update-time {
+		font-size: 22rpx;
+		color: #999;
+	}
+
+	.rating {
+		display: flex;
+		align-items: center;
+		gap: 4rpx;
+	}
+
+	.rating-star {
+		color: #FFD700;
+		font-size: 20rpx;
+	}
+
+	.rating-value {
+		font-size: 20rpx;
+		color: #8B4513;
+		font-weight: 500;
+	}
+
+	.sales-info {
+		text-align: right;
+	}
+
+	.sales-count {
+		font-size: 20rpx;
+		color: #999;
+	}
+
+	.product-footer {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.price-section {
+		display: flex;
+		align-items: baseline;
+		gap: 4rpx;
+	}
+
+	.price-symbol {
+		font-size: 24rpx;
+		color: #8B4513;
+		font-weight: 600;
+	}
+
+	.price-value {
+		font-size: 28rpx;
+		color: #8B4513;
+		font-weight: 700;
+	}
+
+	.original-price {
+		font-size: 24rpx;
+		color: #999;
+		text-decoration: line-through;
+		margin-left: 8rpx;
+	}
+
+	.buy-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		padding: 12rpx 18rpx;
+		border-radius: 16rpx;
+		border: 1rpx solid #FFD700;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+	}
+
+	.buy-btn:active {
+		transform: scale(0.95);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.buy-text {
+		font-size: 23rpx;
+		color: #FFD700;
+		font-weight: 500;
+	}
+
+	/* 空状态 */
+	.empty-state {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 120rpx 40rpx;
+		text-align: center;
+	}
+
+	.empty-icon {
+		font-size: 120rpx;
+		color: rgba(139, 69, 19, 0.3);
+		margin-bottom: 24rpx;
+	}
+
+	.empty-text {
+		font-size: 32rpx;
+		color: #8B4513;
+		font-weight: 600;
+		margin-bottom: 12rpx;
+	}
+
+	.empty-subtext {
+		font-size: 26rpx;
+		color: #A67C52;
+	}
+
+	/* 加载状态 */
+	.loading-tip {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 60rpx 40rpx;
+	}
+
+	.loading-icon {
+		font-size: 48rpx;
+		color: #8B4513;
+		margin-bottom: 16rpx;
+		animation: rotate 2s linear infinite;
+	}
+
+	@keyframes rotate {
+		from {
+			transform: rotate(0deg);
+		}
+
+		to {
+			transform: rotate(360deg);
+		}
+	}
+
+	.loading-text {
+		font-size: 26rpx;
+		color: #8B4513;
+	}
+
+	.no-more-tip {
+		padding: 40rpx;
+	}
+
+	.no-more-decoration {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		gap: 20rpx;
+	}
+
+	.decoration-line {
+		width: 80rpx;
+		height: 1rpx;
+		background: linear-gradient(90deg, transparent, #8B4513, transparent);
+	}
+
+	.decoration-text {
+		font-size: 24rpx;
+		color: #A67C52;
+	}
+
+	/* 响应式设计 */
+	@media (max-width: 750rpx) {
+		.category-list {
+			width: 160rpx;
+		}
+
+		.category-item {
+			padding: 24rpx 16rpx;
+		}
+
+		.category-icon {
+			width: 60rpx;
+			height: 60rpx;
+		}
+
+		.category-image {
+			width: 100%;
+			height: 100%;
+		}
+
+		.icon-symbol {
+			font-size: 24rpx;
+		}
+
+		.category-name {
+			font-size: 22rpx;
+		}
+	}
+</style>

+ 487 - 0
packageShop/pages/settle/index.vue

@@ -0,0 +1,487 @@
+<!-- 结算页面 -->
+<template>
+	<view class="container">
+		<!-- <u-navbar title="确认订单" autoBack></u-navbar> -->
+
+		<!-- 收货地址 -->
+		<view class="address-section" @click="chooseWxAddress">
+			<template v-if="address">
+				<view class="address-content">
+					<view class="info">
+						<text class="name">{{address.userName}}</text>
+						<text class="phone">{{address.telNumber}}</text>
+					</view>
+					<view class="address">
+						{{address.provinceName}}{{address.cityName}}{{address.countyName}}{{address.detailInfo}}
+					</view>
+				</view>
+			</template>
+			<template v-else>
+				<view class="address-empty">
+					<text>请选择收货地址</text>
+					<u-icon name="arrow-right" color="#999"></u-icon>
+				</view>
+			</template>
+		</view>
+
+		<!-- 商品信息 -->
+		<view class="goods-section" v-if="sku">
+			<view class="goods-item" v-for="(item, index) in Array.isArray(sku) ? sku : [sku]" :key="index">
+				<image :src="item.skuImage" mode="aspectFill" class="goods-img"></image>
+				<view class="goods-info">
+					<view class="goods-name">{{item.productName}}</view>
+					<view class="goods-spec">{{item.skuName}}</view>
+					<view class="price-quantity">
+						<text class="price">¥{{item.price}}</text>
+						<text class="quantity">x{{item.count}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 空状态 -->
+		<view class="empty-state" v-else>
+			<text>商品信息不存在</text>
+		</view>
+
+		<!-- 支付方式 -->
+		<view class="payment-section">
+			<view class="section-title">支付方式</view>
+			<view class="payment-list">
+				<view class="payment-item" @click="selectPayment('wxpay')" :class="{active: paymentMethod === 'wxpay'}">
+					<view class="left">
+						<image src="/static/images/wxpay.png" mode="aspectFit" class="payment-icon"></image>
+						<text>微信支付</text>
+					</view>
+					<u-icon v-if="paymentMethod === 'wxpay'" name="checkmark" color="#F95B5B"></u-icon>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单金额 -->
+		<view class="amount-section">
+			<view class="amount-item">
+				<text>商品金额</text>
+				<text class="price">¥{{totalProductAmount}}</text>
+			</view>
+			<view class="amount-item">
+				<text>运费</text>
+				<text class="price">¥{{freightAmount}}</text>
+			</view>
+			<view class="amount-item total">
+				<text>实付款</text>
+				<text class="price">¥{{totalAmount}}</text>
+			</view>
+		</view>
+
+		<!-- 底部提交栏 -->
+		<view class="submit-bar">
+			<view class="total-amount">
+				<text>合计:</text>
+				<text class="price">¥{{totalAmount}}</text>
+			</view>
+			<button class="submit-btn" @click="submitOrder" :loading="submitting" :disabled="submitting">
+				{{submitting ? '提交中...' : '提交订单'}}
+			</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		mapGetters
+	} from 'vuex';
+	import {
+		submitOrder,
+		saveCartOrder
+	} from '../../../config/api.js';
+
+	export default {
+		data() {
+			return {
+				sku: null,
+				address: null,
+				paymentMethod: 'wxpay',
+				submitting: false,
+				freightAmount: 0,
+				isFromCart: false
+			}
+		},
+		computed: {
+			...mapGetters(['isLogin']),
+			totalProductAmount() {
+				if (!this.sku) return 0;
+				if (Array.isArray(this.sku)) {
+					return this.sku.reduce((total, item) => total + (item.price * item.count), 0);
+				}
+				return this.sku.price * this.sku.count;
+			},
+			totalAmount() {
+				return this.totalProductAmount + this.freightAmount;
+			}
+		},
+		onLoad(options) {
+			if (options.sku) {
+				try {
+					this.sku = JSON.parse(decodeURIComponent(options.sku));
+					this.isFromCart = false;
+				} catch (error) {
+					console.error('解析商品数据失败:', error);
+					uni.showToast({
+						title: '商品数据错误',
+						icon: 'none'
+					});
+					setTimeout(() => {
+						uni.navigateBack();
+					}, 1500);
+				}
+			} else if (options.items) {
+				try {
+					const items = JSON.parse(decodeURIComponent(options.items));
+					this.isFromCart = true;
+					// 将购物车商品数据转换为结算页面需要的格式
+					this.sku = items.map(item => ({
+						id: item.id,
+						skuId: item.skuId,
+						skuImage: item.images,
+						productName: item.productName,
+						skuName: item.skuName,
+						price: item.price,
+						count: item.count
+					}));
+				} catch (error) {
+					console.error('解析购物车数据失败:', error);
+					uni.showToast({
+						title: '商品数据错误',
+						icon: 'none'
+					});
+					setTimeout(() => {
+						uni.navigateBack();
+					}, 1500);
+				}
+			} else {
+				uni.showToast({
+					title: '商品数据不存在',
+					icon: 'none'
+				});
+				setTimeout(() => {
+					uni.navigateBack();
+				}, 1500);
+			}
+		},
+		methods: {
+			// 选择微信收货地址
+			chooseWxAddress() {
+				uni.chooseAddress({
+					success: (res) => {
+						this.address = res;
+					},
+					fail: (err) => {
+						console.error('获取收货地址失败:', err);
+						if (err.errMsg.includes('auth deny')) {
+							uni.showModal({
+								title: '提示',
+								content: '需要您的授权才能获取收货地址,是否前往设置?',
+								success: (res) => {
+									if (res.confirm) {
+										uni.openSetting();
+									}
+								}
+							});
+						} else {
+							uni.$u.toast('获取收货地址失败');
+						}
+					}
+				});
+			},
+			// 选择支付方式
+			selectPayment(method) {
+				this.paymentMethod = method;
+			},
+			// 提交订单
+			async submitOrder() {
+				if (!this.address) {
+					return uni.$u.toast('请选择收货地址');
+				}
+
+				if (this.submitting) return;
+				this.submitting = true;
+
+				try {
+					if (this.isFromCart) {
+						// 购物车批量下单
+						const orderList = this.sku.map(item => ({
+							province: this.address.provinceName,
+							city: this.address.cityName,
+							area: this.address.countyName,
+							address: this.address.detailInfo,
+							mobile: this.address.telNumber,
+							name: this.address.userName,
+							skuId: item.skuId,
+							count: item.count,
+							payType: this.paymentMethod === 'wxpay' ? 1 : 2
+						}));
+
+						const res = await saveCartOrder(orderList);
+						if (res.code === 200) {
+							// 跳转到支付页面
+							uni.navigateTo({
+								url: `/packageOrder/pages/payment/index?orderId=${res.data.id}`
+							});
+						} else {
+							uni.$u.toast(res.msg || '提交订单失败');
+						}
+					} else {
+						// 单个商品下单
+						const orderData = {
+							mobile: this.address.telNumber,
+							name: this.address.userName,
+							province: this.address.provinceName,
+							city: this.address.cityName,
+							area: this.address.countyName,
+							address: this.address.detailInfo,
+							postalCode: this.address.postalCode,
+							skuId: this.sku.id,
+							count: this.sku.count,
+							paymentMethod: this.paymentMethod
+						};
+
+						const res = await submitOrder(orderData);
+						if (res.code === 200) {
+							// 跳转到支付页面
+							uni.navigateTo({
+								url: `/packageOrder/pages/payment/index?orderId=${res.data.id}`
+							});
+						} else {
+							uni.$u.toast(res.msg || '提交订单失败');
+						}
+					}
+				} catch (error) {
+					console.error('提交订单失败:', error);
+					uni.$u.toast('提交订单失败,请重试');
+				} finally {
+					this.submitting = false;
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.container {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding-bottom: 120rpx;
+	}
+
+	.address-section {
+		background-color: #fff;
+		margin: 20rpx;
+		padding: 30rpx;
+		border-radius: 12rpx;
+
+		.address-content {
+			.info {
+				margin-bottom: 20rpx;
+
+				.name {
+					font-size: 32rpx;
+					font-weight: bold;
+					margin-right: 20rpx;
+				}
+
+				.phone {
+					font-size: 28rpx;
+					color: #666;
+				}
+			}
+
+			.address {
+				font-size: 28rpx;
+				color: #333;
+			}
+		}
+
+		.address-empty {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			font-size: 28rpx;
+			color: #999;
+		}
+	}
+
+	.goods-section {
+		background-color: #fff;
+		margin: 20rpx;
+		padding: 30rpx;
+		border-radius: 12rpx;
+
+		.goods-item {
+			display: flex;
+
+			.goods-img {
+				width: 160rpx;
+				height: 160rpx;
+				border-radius: 8rpx;
+				margin-bottom: 10rpx;
+			}
+
+			.goods-info {
+				flex: 1;
+				margin-left: 20rpx;
+
+				.goods-name {
+					font-size: 28rpx;
+					color: #333;
+					margin-bottom: 10rpx;
+				}
+
+				.goods-spec {
+					font-size: 24rpx;
+					color: #999;
+					margin-bottom: 20rpx;
+				}
+
+				.price-quantity {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+
+					.price {
+						font-size: 32rpx;
+						color: #F95B5B;
+						font-weight: bold;
+					}
+
+					.quantity {
+						font-size: 26rpx;
+						color: #999;
+					}
+				}
+			}
+		}
+	}
+
+	.payment-section {
+		background-color: #fff;
+		margin: 20rpx;
+		padding: 30rpx;
+		border-radius: 12rpx;
+
+		.section-title {
+			font-size: 28rpx;
+			color: #333;
+			margin-bottom: 20rpx;
+		}
+
+		.payment-item {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 20rpx 0;
+
+			&.active {
+				.left {
+					text {
+						color: #F95B5B;
+					}
+				}
+			}
+
+			.left {
+				display: flex;
+				align-items: center;
+
+				.payment-icon {
+					width: 40rpx;
+					height: 40rpx;
+					margin-right: 20rpx;
+				}
+
+				text {
+					font-size: 28rpx;
+					color: #333;
+				}
+			}
+		}
+	}
+
+	.amount-section {
+		background-color: #fff;
+		margin: 20rpx;
+		padding: 30rpx;
+		border-radius: 12rpx;
+
+		.amount-item {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 20rpx;
+			font-size: 28rpx;
+			color: #666;
+
+			&.total {
+				margin-top: 20rpx;
+				padding-top: 20rpx;
+				border-top: 1px solid #f5f5f5;
+				color: #333;
+				font-weight: bold;
+
+				.price {
+					color: #F95B5B;
+					font-size: 32rpx;
+				}
+			}
+
+			.price {
+				color: #333;
+			}
+		}
+	}
+
+	.submit-bar {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 100rpx;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		padding: 0 30rpx;
+		box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+		.total-amount {
+			flex: 1;
+			font-size: 28rpx;
+
+			.price {
+				color: #F95B5B;
+				font-size: 36rpx;
+				font-weight: bold;
+			}
+		}
+
+		.submit-btn {
+			width: 240rpx;
+			height: 72rpx;
+			line-height: 72rpx;
+			background-color: #F95B5B;
+			color: #fff;
+			font-size: 28rpx;
+			border-radius: 36rpx;
+			text-align: center;
+
+			&[disabled] {
+				background-color: #ccc;
+			}
+		}
+	}
+
+	.empty-state {
+		text-align: center;
+		padding: 40rpx;
+		color: #999;
+		font-size: 28rpx;
+	}
+</style>

+ 165 - 0
packageUser/pages/about/index.vue

@@ -0,0 +1,165 @@
+<template>
+	<view class="about-page">
+		<view class="header">
+			<image class="logo" src="/static/images/logo/c1f9fb42be9c750950c8d7e89a68eba.jpg" mode="aspectFit"></image>
+			<text class="app-name">七指堂商城</text>
+			<text class="version">版本 1.0.0</text>
+		</view>
+		
+		<view class="content">
+			<view class="section">
+				<text class="title">关于我们</text>
+				<text class="text">七指堂商城是一家专注于提供优质商品和服务的电商平台。我们致力于为用户提供优质的购物体验,让每一位用户都能买到称心如意的商品。</text>
+			</view>
+			
+			<view class="section">
+				<text class="title">联系我们</text>
+				<view class="contact-item">
+					<text class="label">客服电话:</text>
+					<text class="value" @tap="makeCall">400-123-4567</text>
+				</view>
+				<view class="contact-item">
+					<text class="label">商务合作:</text>
+					<text class="value">business@qizhitang.com</text>
+				</view>
+				<view class="contact-item">
+					<text class="label">公司地址:</text>
+					<text class="value">广东省广州市天河区科韵路XX号</text>
+				</view>
+			</view>
+			
+			<view class="links">
+				<view class="link-item" @tap="goToPrivacy">
+					<text>隐私政策</text>
+					<text class="iconfont arrow">&#xe685;</text>
+				</view>
+				<view class="link-item" @tap="goToService">
+					<text>服务协议</text>
+					<text class="iconfont arrow">&#xe685;</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	methods: {
+		makeCall() {
+			uni.makePhoneCall({
+				phoneNumber: '4001234567'
+			});
+		},
+		
+		goToPrivacy() {
+			uni.navigateTo({
+				url: '/packageUser/pages/article/detail?type=privacy'
+			});
+		},
+		
+		goToService() {
+			uni.navigateTo({
+				url: '/packageUser/pages/article/detail?type=service'
+			});
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.about-page {
+	min-height: 100vh;
+	background: #f5f5f5;
+}
+
+.header {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 60rpx 0;
+	background: #fff;
+	
+	.logo {
+		width: 160rpx;
+		height: 160rpx;
+		border-radius: 40rpx;
+	}
+	
+	.app-name {
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #333;
+		margin-top: 20rpx;
+	}
+	
+	.version {
+		font-size: 24rpx;
+		color: #999;
+		margin-top: 10rpx;
+	}
+}
+
+.content {
+	margin-top: 20rpx;
+	
+	.section {
+		background: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		
+		.title {
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333;
+			margin-bottom: 20rpx;
+		}
+		
+		.text {
+			font-size: 28rpx;
+			color: #666;
+			line-height: 1.6;
+		}
+		
+		.contact-item {
+			display: flex;
+			margin-top: 16rpx;
+			
+			.label {
+				font-size: 28rpx;
+				color: #333;
+			}
+			
+			.value {
+				font-size: 28rpx;
+				color: #666;
+			}
+		}
+	}
+	
+	.links {
+		background: #fff;
+		
+		.link-item {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 30rpx;
+			border-bottom: 1px solid #eee;
+			
+			text {
+				font-size: 28rpx;
+				color: #333;
+			}
+			
+			.arrow {
+				color: #999;
+				transform: rotate(180deg);
+			}
+			
+			&:last-child {
+				border-bottom: none;
+			}
+		}
+	}
+}
+</style> 

+ 205 - 0
packageUser/pages/address/edit.vue

@@ -0,0 +1,205 @@
+<template>
+  <view class="address-edit">
+    <u-form :model="form" ref="form" :rules="rules">
+      <u-form-item label="收货人" prop="name" required>
+        <u-input v-model="form.name" placeholder="请输入收货人姓名" />
+      </u-form-item>
+      
+      <u-form-item label="手机号码" prop="phone" required>
+        <u-input v-model="form.phone" placeholder="请输入手机号码" type="number" maxlength="11" />
+      </u-form-item>
+      
+      <u-form-item label="所在地区" prop="region" required @click="showRegionPicker = true">
+        <view class="region-select">
+          <text v-if="form.province">{{form.province + form.city + form.district}}</text>
+          <text v-else class="placeholder">请选择所在地区</text>
+          <u-icon name="arrow-right"></u-icon>
+        </view>
+      </u-form-item>
+      
+      <u-form-item label="详细地址" prop="address" required>
+        <u-textarea v-model="form.address" placeholder="请输入详细地址信息" />
+      </u-form-item>
+      
+      <u-form-item>
+        <u-switch v-model="form.isDefault" activeColor="#F95B5B">
+          <text slot="label" class="switch-label">设为默认地址</text>
+        </u-switch>
+      </u-form-item>
+    </u-form>
+
+    <view class="submit-btn">
+      <u-button type="primary" @click="submit" color="#F95B5B">保存</u-button>
+      <u-button v-if="form.id" @click="deleteAddress" type="error">删除</u-button>
+    </view>
+
+    <u-picker
+      :show="showRegionPicker"
+      :columns="regionColumns"
+      @confirm="confirmRegion"
+      @cancel="showRegionPicker = false"
+      title="选择地区">
+    </u-picker>
+    
+    <u-modal
+      :show="showDeleteModal"
+      @confirm="confirmDelete"
+      @cancel="showDeleteModal = false"
+      title="删除确认"
+      content="确定要删除该收货地址吗?"
+      showCancelButton>
+    </u-modal>
+  </view>
+</template>
+
+<script>
+import { addressDetail, addressDel, addressSubmit } from '../../../config/api.js';
+
+export default {
+  data() {
+    return {
+      form: {
+        name: '',
+        phone: '',
+        province: '',
+        city: '',
+        district: '',
+        address: '',
+        isDefault: false
+      },
+      showRegionPicker: false,
+      showDeleteModal: false,
+      regionColumns: [
+        // 这里需要接入省市区数据
+        // 可以使用第三方包或者自行维护数据
+      ],
+      rules: {
+        name: [{
+          required: true,
+          message: '请输入收货人姓名',
+          trigger: ['blur', 'change']
+        }],
+        phone: [{
+          required: true,
+          message: '请输入手机号码',
+          trigger: ['blur', 'change']
+        }, {
+          pattern: /^1[3-9]\d{9}$/,
+          message: '请输入正确的手机号码',
+          trigger: ['blur', 'change']
+        }],
+        region: [{
+          required: true,
+          message: '请选择所在地区',
+          trigger: ['blur', 'change']
+        }],
+        address: [{
+          required: true,
+          message: '请输入详细地址',
+          trigger: ['blur', 'change']
+        }]
+      }
+    }
+  },
+  onLoad(options) {
+    if (options.id) {
+      this.getAddressDetail(options.id)
+    }
+  },
+  methods: {
+    async getAddressDetail(id) {
+      try {
+        const res = await addressDetail({ id })
+        if (res.code === 200 && res.data) {
+          this.form = res.data
+        }
+      } catch (e) {
+        uni.$u.toast('获取地址信息失败')
+      }
+    },
+    confirmRegion(e) {
+      const [province, city, district] = e.value
+      this.form.province = province
+      this.form.city = city
+      this.form.district = district
+      this.showRegionPicker = false
+    },
+    deleteAddress() {
+      this.showDeleteModal = true
+    },
+    async confirmDelete() {
+      try {
+        const res = await addressDel({
+          ids: this.form.id
+        })
+        if (res.code === 200) {
+          uni.$u.toast('删除成功')
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 1500)
+        }
+      } catch (e) {
+        uni.$u.toast('删除失败')
+      }
+      this.showDeleteModal = false
+    },
+    async submit() {
+      try {
+        await this.$refs.form.validate()
+        const res = await addressSubmit(this.form)
+        if (res.code === 200) {
+          uni.$u.toast(this.form.id ? '修改成功' : '添加成功')
+          setTimeout(() => {
+            uni.navigateBack()
+          }, 1500)
+        }
+      } catch (e) {
+        if (e.errors) return
+        uni.$u.toast(this.form.id ? '修改失败' : '添加失败')
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.address-edit {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 20rpx;
+  
+  :deep(.u-form) {
+    background: #fff;
+    border-radius: 12rpx;
+    padding: 20rpx;
+    
+    .u-form-item {
+      padding: 20rpx 0;
+    }
+  }
+  
+  .region-select {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    
+    .placeholder {
+      color: #c0c4cc;
+    }
+  }
+  
+  .switch-label {
+    font-size: 28rpx;
+    margin-right: 20rpx;
+  }
+  
+  .submit-btn {
+    margin-top: 40rpx;
+    padding: 0 20rpx;
+    
+    .u-button {
+      margin-bottom: 20rpx;
+    }
+  }
+}
+</style> 

+ 195 - 0
packageUser/pages/address/list.vue

@@ -0,0 +1,195 @@
+<template>
+  <view class="address-container">
+    <u-empty v-if="addressList.length === 0" mode="address" icon="/static/images/empty/address.png">
+      <view class="empty-tips">暂无收货地址</view>
+    </u-empty>
+    
+    <view v-else class="address-list">
+      <view v-for="(item, index) in addressList" :key="index" class="address-item">
+        <view class="address-info" @click="selectAddress(item)">
+          <view class="user-info">
+            <text class="name">{{item.name}}</text>
+            <text class="phone">{{item.phone}}</text>
+            <text v-if="item.isDefault" class="default-tag">默认</text>
+          </view>
+          <view class="address-detail">{{item.province}}{{item.city}}{{item.district}}{{item.address}}</view>
+        </view>
+        <view class="operation">
+          <view class="edit" @click="editAddress(item)">
+            <u-icon name="edit-pen" size="40rpx"></u-icon>
+          </view>
+          <view class="delete" @click="deleteAddress(item)">
+            <u-icon name="trash" size="40rpx"></u-icon>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="add-btn">
+      <u-button type="primary" text="新增收货地址" @click="addAddress" color="#F95B5B"></u-button>
+    </view>
+
+    <u-modal
+      :show="showDeleteModal"
+      @confirm="confirmDelete"
+      @cancel="showDeleteModal = false"
+      title="删除确认"
+      content="确定要删除该收货地址吗?"
+      showCancelButton>
+    </u-modal>
+  </view>
+</template>
+
+<script>
+import { addressList, addressDel } from '../../../config/api.js';
+
+export default {
+  data() {
+    return {
+      addressList: [],
+      showDeleteModal: false,
+      currentAddress: null,
+      isSelect: false
+    }
+  },
+  onLoad(options) {
+    if (options.select) {
+      this.isSelect = true
+    }
+  },
+  onShow() {
+    this.getAddressList()
+  },
+  methods: {
+    async getAddressList() {
+      try {
+        const res = await addressList()
+        if (res.code === 200) {
+          this.addressList = res.data || []
+        }
+      } catch (e) {
+        uni.$u.toast('获取地址列表失败')
+      }
+    },
+    addAddress() {
+      uni.navigateTo({
+        url: '/packageUser/pages/address/edit'
+      })
+    },
+    editAddress(item) {
+      uni.navigateTo({
+        url: '/packageUser/pages/address/edit?id=' + item.id
+      })
+    },
+    deleteAddress(item) {
+      this.currentAddress = item
+      this.showDeleteModal = true
+    },
+    async confirmDelete() {
+      if (!this.currentAddress) return
+      try {
+        const res = await addressDel({
+          ids: this.currentAddress.id
+        })
+        if (res.code === 200) {
+          uni.$u.toast('删除成功')
+          this.getAddressList()
+        }
+      } catch (e) {
+        uni.$u.toast('删除失败')
+      }
+      this.showDeleteModal = false
+      this.currentAddress = null
+    },
+    selectAddress(item) {
+      if (!this.isSelect) return
+      
+      const pages = getCurrentPages()
+      const prevPage = pages[pages.length - 2]
+      
+      if (prevPage && prevPage.$vm.setSelectedAddress) {
+        prevPage.$vm.setSelectedAddress(item)
+        uni.navigateBack()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.address-container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 120rpx;
+  
+  .address-list {
+    padding: 20rpx;
+    
+    .address-item {
+      background: #fff;
+      border-radius: 12rpx;
+      padding: 30rpx;
+      margin-bottom: 20rpx;
+      
+      .address-info {
+        .user-info {
+          margin-bottom: 16rpx;
+          
+          .name {
+            font-size: 32rpx;
+            font-weight: bold;
+            margin-right: 20rpx;
+          }
+          
+          .phone {
+            font-size: 28rpx;
+            color: #666;
+          }
+          
+          .default-tag {
+            background: #F95B5B;
+            color: #fff;
+            font-size: 20rpx;
+            padding: 4rpx 12rpx;
+            border-radius: 4rpx;
+            margin-left: 20rpx;
+          }
+        }
+        
+        .address-detail {
+          font-size: 28rpx;
+          color: #333;
+          line-height: 1.4;
+        }
+      }
+      
+      .operation {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 20rpx;
+        border-top: 1rpx solid #eee;
+        padding-top: 20rpx;
+        
+        .edit, .delete {
+          padding: 10rpx 20rpx;
+        }
+      }
+    }
+  }
+  
+  .add-btn {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 20rpx 40rpx;
+    background: #fff;
+  }
+  
+  .empty-tips {
+    font-size: 28rpx;
+    color: #999;
+    margin-top: 20rpx;
+  }
+}
+</style> 

+ 867 - 0
packageUser/pages/collect/index.vue

@@ -0,0 +1,867 @@
+<template>
+	<view class="collect-page">
+
+
+		<!-- 搜索栏 -->
+		<view class="search-section">
+			<view class="search-container">
+				<u-search v-model="searchKeyword" placeholder="搜索收藏的商品" :showAction="true" actionText="搜索"
+					@search="handleSearch" @custom="handleSearch" :clearabled="true" @clear="handleClear"></u-search>
+			</view>
+			<!-- 批量操作栏 -->
+			<view v-if="collectList.length > 0" class="batch-actions">
+				<view class="batch-info">
+					<text class="batch-text">共 {{ collectList.length }} 件收藏商品</text>
+				</view>
+				<view class="batch-buttons">
+					<view class="batch-btn" @click="selectAll">
+						<text class="batch-btn-text">{{ isAllSelected ? '取消全选' : '全选' }}</text>
+					</view>
+					<view v-if="selectedItems.length > 0" class="batch-btn delete-btn" @click="batchCancelCollect">
+						<text class="batch-btn-text">取消收藏({{ selectedItems.length }})</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 商品列表 -->
+		<scroll-view class="product-list" scroll-y @scrolltolower="loadMore" :refresher-enabled="true"
+			:refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
+			<!-- 商品卡片 -->
+			<view v-if="collectList.length > 0" class="product-container">
+				<view v-for="(item, index) in collectList" :key="item.id" class="product-card"
+					@click="goToDetail(item.id)">
+					<!-- 选择框 -->
+					<view class="select-checkbox" :class="{ 'selected': selectedItems.includes(item.id) }"
+						@click.stop="toggleSelect(item.id)">
+						<view v-if="selectedItems.includes(item.id)" class="checkmark">✓</view>
+					</view>
+
+					<!-- 商品图片 -->
+					<view class="product-image-container">
+						<image :src="item.images" mode="aspectFill" class="product-image" @error="handleImageError">
+						</image>
+						<!-- 取消收藏按钮 -->
+						<view class="collect-btn" @click.stop="cancelCollect(item, index)">
+							<u-icon name="heart-fill" size="24" color="#FF4B4B"></u-icon>
+						</view>
+					</view>
+
+					<!-- 商品信息 -->
+					<view class="product-info">
+						<view class="product-name">{{ item.name }}</view>
+						<view class="product-desc" v-if="item.description">{{ item.description }}</view>
+
+						<!-- 商品评分和销量 -->
+						<view class="product-meta">
+							<view class="rating-section" v-if="item.productAvg">
+								<u-rate :value="item.productAvg" readonly allowHalf size="12"
+									activeColor="#FFD700"></u-rate>
+								<text class="rating-text">{{ item.productAvg }}</text>
+							</view>
+							<view class="sales-info">
+								<text class="sales-text">已售{{ item.salesTotal || 0 }}件</text>
+							</view>
+						</view>
+
+						<!-- 价格区域 -->
+						<view class="price-section">
+							<view class="price-info">
+								<text class="price-symbol">¥</text>
+								<text class="price-value">{{ item.price }}</text>
+								<text class="original-price"
+									v-if="item.originalPrice && item.originalPrice > item.price">
+									¥{{ item.originalPrice }}
+								</text>
+							</view>
+							<view class="action-buttons">
+								<view class="buy-btn" @click.stop="goToDetail(item.id)">
+									<text class="buy-text">立即购买</text>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 空状态 -->
+			<view v-else-if="!isLoading" class="empty-state">
+				<u-empty mode="favor" icon="/static/images/empty.png" text="暂无收藏商品">
+					<view class="empty-tips">
+						<text class="tips-text">快去收藏喜欢的商品吧</text>
+						<view class="go-shop-btn" @click="goToShop">
+							<text class="btn-text">去逛逛</text>
+						</view>
+					</view>
+				</u-empty>
+			</view>
+
+			<!-- 网络错误状态 -->
+			<view v-else-if="networkError" class="error-state">
+				<u-empty mode="wifi" icon="/static/images/empty.png" text="网络连接失败">
+					<view class="error-tips">
+						<text class="error-text">请检查网络连接后重试</text>
+						<view class="retry-btn" @click="retryLoad">
+							<text class="retry-text">重新加载</text>
+						</view>
+					</view>
+				</u-empty>
+			</view>
+
+			<!-- 加载状态 -->
+			<view v-if="isLoading && collectList.length === 0" class="loading-state">
+				<u-loading-icon mode="spinner" size="28"></u-loading-icon>
+				<text class="loading-text">加载中...</text>
+			</view>
+
+			<!-- 加载更多状态 -->
+			<view v-if="collectList.length > 0" class="load-more-state">
+				<view v-if="isLoading" class="loading-more">
+					<u-loading-icon mode="spinner" size="20"></u-loading-icon>
+					<text class="loading-more-text">加载中...</text>
+				</view>
+				<view v-else-if="!hasMore" class="no-more">
+					<text class="no-more-text">—— 已加载全部收藏 ——</text>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	import {
+		productCollectList,
+		productClearCollect
+	} from '@/config/api.js';
+
+	export default {
+		data() {
+			return {
+				bgColor: '#ffffff',
+				collectList: [], // 收藏商品列表
+				searchKeyword: '', // 搜索关键词
+				isLoading: false, // 是否正在加载
+				isRefreshing: false, // 是否正在刷新
+				hasMore: true, // 是否还有更多数据
+				selectedItems: [], // 选中的商品ID列表
+				networkError: false, // 网络错误状态
+				params: {
+					current: 1, // 当前页码
+					size: 10, // 每页数量
+					name: '' // 商品名称搜索
+				}
+			}
+		},
+		onLoad() {
+			uni.showLoading({
+				title: '加载中...'
+			})
+			this.getCollectList().finally(() => {
+				uni.hideLoading()
+			})
+		},
+		onShow() {
+			// 页面显示时刷新数据
+			this.refreshData()
+		},
+		onShareAppMessage() {
+			return {
+				title: '我的收藏',
+				path: '/packageUser/pages/collect/index'
+			}
+		},
+		onShareTimeline() {
+			return {
+				title: '我的收藏'
+			}
+		},
+		computed: {
+			// 是否全选
+			isAllSelected() {
+				return this.collectList.length > 0 && this.selectedItems.length === this.collectList.length
+			}
+		},
+		methods: {
+			// 获取收藏列表
+			async getCollectList(isLoadMore = false) {
+				if (this.isLoading) return
+
+				this.isLoading = true
+				this.networkError = false
+
+				try {
+					// 根据API定义,productCollectList是POST请求,需要传递参数
+					const params = {
+						current: this.params.current,
+						size: this.params.size,
+						name: this.params.name
+					}
+
+					const res = await productCollectList(params)
+
+					if (res.code === 200) {
+						// 处理返回的数据结构
+						let newList = []
+						if (res.data && res.data.records) {
+							// 分页数据结构
+							newList = res.data.records
+						} else if (Array.isArray(res.data)) {
+							// 直接数组结构
+							newList = res.data
+						} else if (res.data && Array.isArray(res.data.list)) {
+							// 其他可能的数据结构
+							newList = res.data.list
+						}
+
+						// 确保newList是数组
+						if (!Array.isArray(newList)) {
+							newList = []
+						}
+
+						if (isLoadMore) {
+							this.collectList = [...this.collectList, ...newList]
+						} else {
+							this.collectList = newList
+						}
+
+						// 判断是否还有更多数据
+						this.hasMore = newList.length === this.params.size
+					} else {
+						uni.showToast({
+							title: res.msg || '获取收藏列表失败',
+							icon: 'none'
+						})
+					}
+				} catch (error) {
+					console.error('获取收藏列表失败:', error)
+					this.networkError = true
+					uni.showToast({
+						title: '网络错误,请重试',
+						icon: 'none'
+					})
+				} finally {
+					this.isLoading = false
+					this.isRefreshing = false
+				}
+			},
+
+			// 搜索处理
+			handleSearch() {
+				this.params.name = this.searchKeyword
+				this.params.current = 1
+				this.hasMore = true
+				this.selectedItems = [] // 清空选中状态
+				this.getCollectList()
+			},
+
+			// 清除搜索
+			handleClear() {
+				this.searchKeyword = ''
+				this.params.name = ''
+				this.params.current = 1
+				this.hasMore = true
+				this.selectedItems = [] // 清空选中状态
+				this.getCollectList()
+			},
+
+			// 取消收藏
+			async cancelCollect(item, index) {
+				uni.showModal({
+					title: '提示',
+					content: '确定要取消收藏这个商品吗?',
+					success: async (res) => {
+						if (res.confirm) {
+							try {
+								const params = {
+									productId: item.id
+								}
+
+								const result = await productClearCollect(params)
+
+								if (result.code === 200) {
+									// 从列表中移除
+									this.collectList.splice(index, 1)
+
+									uni.showToast({
+										title: '已取消收藏',
+										icon: 'success'
+									})
+
+									// 如果列表为空,刷新数据
+									if (this.collectList.length === 0) {
+										this.refreshData()
+									}
+								} else {
+									uni.showToast({
+										title: result.msg || '取消收藏失败',
+										icon: 'none'
+									})
+								}
+							} catch (error) {
+								console.error('取消收藏失败:', error)
+								uni.showToast({
+									title: '网络错误,请重试',
+									icon: 'none'
+								})
+							}
+						}
+					}
+				})
+			},
+
+			// 加载更多
+			loadMore() {
+				if (this.isLoading || !this.hasMore) return
+
+				this.params.current++
+				this.getCollectList(true)
+			},
+
+			// 下拉刷新
+			async onRefresh() {
+				this.isRefreshing = true
+				this.params.current = 1
+				this.hasMore = true
+				await this.getCollectList()
+			},
+
+			// 刷新数据
+			refreshData() {
+				this.params.current = 1
+				this.hasMore = true
+				this.selectedItems = [] // 清空选中状态
+				this.getCollectList()
+			},
+
+			// 跳转到商品详情
+			goToDetail(productId) {
+				uni.navigateTo({
+					url: `/packageShop/pages/detail/index?id=${productId}`
+				})
+			},
+
+			// 跳转到商城
+			goToShop() {
+				uni.switchTab({
+					url: '/pages/shop/product-type-list'
+				})
+			},
+
+			// 图片加载错误处理
+			handleImageError() {
+				// 可以设置默认图片
+				console.log('图片加载失败')
+			},
+
+			// 重试加载
+			retryLoad() {
+				this.networkError = false
+				this.refreshData()
+			},
+
+			// 切换选择状态
+			toggleSelect(productId) {
+				const index = this.selectedItems.indexOf(productId)
+				if (index > -1) {
+					this.selectedItems.splice(index, 1)
+				} else {
+					this.selectedItems.push(productId)
+				}
+			},
+
+			// 全选/取消全选
+			selectAll() {
+				if (this.isAllSelected) {
+					this.selectedItems = []
+				} else {
+					this.selectedItems = this.collectList.map(item => item.id)
+				}
+			},
+
+			// 批量取消收藏
+			async batchCancelCollect() {
+				if (this.selectedItems.length === 0) {
+					uni.showToast({
+						title: '请选择要取消收藏的商品',
+						icon: 'none'
+					})
+					return
+				}
+
+				uni.showModal({
+					title: '提示',
+					content: `确定要取消收藏选中的 ${this.selectedItems.length} 件商品吗?`,
+					success: async (res) => {
+						if (res.confirm) {
+							try {
+								uni.showLoading({
+									title: '处理中...'
+								})
+
+								// 逐个取消收藏
+								const promises = this.selectedItems.map(productId => {
+									return productClearCollect({
+										productId
+									})
+								})
+
+								await Promise.all(promises)
+
+								// 从列表中移除选中的商品
+								this.collectList = this.collectList.filter(item =>
+									!this.selectedItems.includes(item.id)
+								)
+
+								// 清空选中列表
+								this.selectedItems = []
+
+								uni.hideLoading()
+								uni.showToast({
+									title: '批量取消收藏成功',
+									icon: 'success'
+								})
+
+								// 如果列表为空,刷新数据
+								if (this.collectList.length === 0) {
+									this.refreshData()
+								}
+							} catch (error) {
+								uni.hideLoading()
+								console.error('批量取消收藏失败:', error)
+								uni.showToast({
+									title: '批量操作失败,请重试',
+									icon: 'none'
+								})
+							}
+						}
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.collect-page {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+	}
+
+	.search-section {
+		padding: 20rpx;
+		background-color: #fff;
+		border-bottom: 1rpx solid #eee;
+	}
+
+	.search-container {
+		background-color: #f8f8f8;
+		border-radius: 20rpx;
+		overflow: hidden;
+	}
+
+	.batch-actions {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20rpx 0;
+		border-top: 1rpx solid #eee;
+		margin-top: 20rpx;
+	}
+
+	.batch-info {
+		flex: 1;
+	}
+
+	.batch-text {
+		font-size: 26rpx;
+		color: #666;
+	}
+
+	.batch-buttons {
+		display: flex;
+		gap: 20rpx;
+	}
+
+	.batch-btn {
+		padding: 12rpx 24rpx;
+		border-radius: 20rpx;
+		background-color: #f0f0f0;
+		transition: all 0.3s ease;
+	}
+
+	.batch-btn:active {
+		transform: scale(0.95);
+	}
+
+	.batch-btn-text {
+		font-size: 24rpx;
+		color: #333;
+	}
+
+	.delete-btn {
+		background-color: #FFE6E6;
+	}
+
+	.delete-btn .batch-btn-text {
+		color: #FF4B4B;
+	}
+
+	.product-list {
+		flex: 1;
+		height: calc(100vh - 200rpx);
+	}
+
+	.product-container {
+		padding: 20rpx;
+	}
+
+	.product-card {
+		background-color: #fff;
+		border-radius: 16rpx;
+		margin-bottom: 20rpx;
+		overflow: hidden;
+		box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
+		transition: all 0.3s ease;
+		position: relative;
+	}
+
+	/* 移除商品卡片的选中状态样式,只保留选择框的选中状态 */
+
+	.select-checkbox {
+		position: absolute;
+		top: 20rpx;
+		left: 20rpx;
+		z-index: 10;
+		width: 40rpx;
+		height: 40rpx;
+		background-color: rgba(255, 255, 255, 0.9);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		backdrop-filter: blur(10rpx);
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+		transition: all 0.3s ease;
+		border: 2rpx solid #ccc;
+	}
+
+	.select-checkbox:active {
+		transform: scale(0.9);
+	}
+
+	.select-checkbox.selected {
+		border-color: #FF4B4B;
+		background-color: #FF4B4B;
+	}
+
+	.checkmark {
+		color: #fff;
+		font-size: 20rpx;
+		font-weight: bold;
+		line-height: 1;
+	}
+
+	.product-card:active {
+		transform: scale(0.98);
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+	}
+
+	.product-image-container {
+		position: relative;
+		width: 100%;
+		height: 400rpx;
+		overflow: hidden;
+	}
+
+	.product-image {
+		width: 100%;
+		height: 100%;
+		transition: transform 0.3s ease;
+	}
+
+	.product-card:active .product-image {
+		transform: scale(1.05);
+	}
+
+	.collect-btn {
+		position: absolute;
+		top: 20rpx;
+		right: 20rpx;
+		width: 60rpx;
+		height: 60rpx;
+		background-color: rgba(255, 255, 255, 0.9);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		backdrop-filter: blur(10rpx);
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+		transition: all 0.3s ease;
+	}
+
+	.collect-btn:active {
+		transform: scale(0.9);
+		background-color: rgba(255, 255, 255, 1);
+	}
+
+	.product-info {
+		padding: 24rpx;
+	}
+
+	.product-name {
+		font-size: 30rpx;
+		color: #333;
+		font-weight: 600;
+		line-height: 1.4;
+		margin-bottom: 12rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.product-desc {
+		font-size: 24rpx;
+		color: #666;
+		margin-bottom: 16rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+		line-height: 1.4;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.product-meta {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 20rpx;
+	}
+
+	.rating-section {
+		display: flex;
+		align-items: center;
+		gap: 8rpx;
+	}
+
+	.rating-text {
+		font-size: 22rpx;
+		color: #666;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.sales-info {
+		text-align: right;
+		display: flex;
+		align-items: center;
+		justify-content: flex-end;
+	}
+
+	.sales-text {
+		font-size: 22rpx;
+		color: #999;
+		white-space: nowrap;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.price-section {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.price-info {
+		display: flex;
+		align-items: baseline;
+		gap: 4rpx;
+	}
+
+	.price-symbol {
+		font-size: 24rpx;
+		color: #FF4B4B;
+		font-weight: 600;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.price-value {
+		font-size: 36rpx;
+		color: #FF4B4B;
+		font-weight: 700;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.original-price {
+		font-size: 24rpx;
+		color: #999;
+		text-decoration: line-through;
+		margin-left: 8rpx;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.action-buttons {
+		display: flex;
+		gap: 12rpx;
+	}
+
+	.buy-btn {
+		background: linear-gradient(135deg, #FF4B4B 0%, #FF6B6B 100%);
+		border-radius: 20rpx;
+		padding: 12rpx 24rpx;
+		transition: all 0.3s ease;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		min-width: 80rpx;
+		height: 60rpx;
+	}
+
+	.buy-btn:active {
+		transform: scale(0.95);
+		background: linear-gradient(135deg, #E63939 0%, #E65959 100%);
+	}
+
+	.buy-text {
+		color: #fff;
+		font-size: 24rpx;
+		font-weight: 600;
+		white-space: nowrap;
+		writing-mode: horizontal-tb;
+		text-orientation: mixed;
+	}
+
+	.empty-state {
+		padding: 100rpx 40rpx;
+		text-align: center;
+	}
+
+	.empty-tips {
+		margin-top: 30rpx;
+	}
+
+	.tips-text {
+		font-size: 28rpx;
+		color: #999;
+		display: block;
+		margin-bottom: 30rpx;
+	}
+
+	.go-shop-btn {
+		background: linear-gradient(135deg, #FF4B4B 0%, #FF6B6B 100%);
+		border-radius: 25rpx;
+		padding: 16rpx 40rpx;
+		display: inline-block;
+		transition: all 0.3s ease;
+	}
+
+	.go-shop-btn:active {
+		transform: scale(0.95);
+	}
+
+	.btn-text {
+		color: #fff;
+		font-size: 28rpx;
+		font-weight: 600;
+	}
+
+	.error-state {
+		padding: 100rpx 40rpx;
+		text-align: center;
+	}
+
+	.error-tips {
+		margin-top: 30rpx;
+	}
+
+	.error-text {
+		font-size: 28rpx;
+		color: #999;
+		display: block;
+		margin-bottom: 30rpx;
+	}
+
+	.retry-btn {
+		background: linear-gradient(135deg, #007AFF 0%, #5AC8FA 100%);
+		border-radius: 25rpx;
+		padding: 16rpx 40rpx;
+		display: inline-block;
+		transition: all 0.3s ease;
+	}
+
+	.retry-btn:active {
+		transform: scale(0.95);
+	}
+
+	.retry-text {
+		color: #fff;
+		font-size: 28rpx;
+		font-weight: 600;
+	}
+
+	.loading-state {
+		padding: 100rpx 40rpx;
+		text-align: center;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		gap: 20rpx;
+	}
+
+	.loading-text {
+		font-size: 28rpx;
+		color: #999;
+	}
+
+	.load-more-state {
+		padding: 40rpx 20rpx;
+		text-align: center;
+	}
+
+	.loading-more {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		gap: 12rpx;
+	}
+
+	.loading-more-text {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.no-more {
+		text-align: center;
+	}
+
+	.no-more-text {
+		font-size: 24rpx;
+		color: #999;
+		position: relative;
+	}
+
+	.no-more-text::before,
+	.no-more-text::after {
+		content: '';
+		position: absolute;
+		top: 50%;
+		width: 60rpx;
+		height: 1rpx;
+		background-color: #ddd;
+	}
+
+	.no-more-text::before {
+		left: -80rpx;
+	}
+
+	.no-more-text::after {
+		right: -80rpx;
+	}
+</style>

+ 223 - 0
packageUser/pages/feedback/index.vue

@@ -0,0 +1,223 @@
+<template>
+	<view class="feedback-page">
+		<view class="header">
+			<text class="title">意见反馈</text>
+		</view>
+		
+		<view class="form-section">
+			<textarea class="feedback-input" 
+				v-model="content" 
+				placeholder="请输入您的宝贵意见,我们会认真查看并及时处理..."
+				:maxlength="500"
+			></textarea>
+			<view class="word-count">{{content.length}}/500</view>
+			
+			<view class="image-upload">
+				<view class="title">图片上传(选填)</view>
+				<view class="image-list">
+					<view class="image-item" v-for="(item, index) in images" :key="index">
+						<image :src="item" mode="aspectFill"></image>
+						<view class="delete-btn" @tap="deleteImage(index)">×</view>
+					</view>
+					<view class="upload-btn" @tap="chooseImage" v-if="images.length < 4">
+						<text class="iconfont">&#xe685;</text>
+						<text>上传图片</text>
+					</view>
+				</view>
+			</view>
+			
+			<view class="contact-input">
+				<input type="text" v-model="contact" placeholder="请留下您的联系方式(选填)" />
+			</view>
+			
+			<button class="submit-btn" @tap="handleSubmit">提交反馈</button>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			content: '',
+			images: [],
+			contact: ''
+		}
+	},
+	methods: {
+		// 选择图片
+		chooseImage() {
+			uni.chooseImage({
+				count: 4 - this.images.length,
+				success: (res) => {
+					this.images = [...this.images, ...res.tempFilePaths];
+				}
+			});
+		},
+		
+		// 删除图片
+		deleteImage(index) {
+			this.images.splice(index, 1);
+		},
+		
+		// 提交反馈
+		handleSubmit() {
+			if (!this.content) {
+				uni.showToast({
+					title: '请输入反馈内容',
+					icon: 'none'
+				});
+				return;
+			}
+			
+			uni.showLoading({
+				title: '提交中...'
+			});
+			
+			// TODO: 调用提交反馈接口
+			setTimeout(() => {
+				uni.hideLoading();
+				uni.showToast({
+					title: '提交成功',
+					icon: 'success'
+				});
+				
+				setTimeout(() => {
+					uni.navigateBack();
+				}, 1500);
+			}, 1000);
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.feedback-page {
+	min-height: 100vh;
+	background: #f5f5f5;
+}
+
+.header {
+	background: #fff;
+	padding: 20rpx 30rpx;
+	
+	.title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.form-section {
+	margin-top: 20rpx;
+	padding: 30rpx;
+	background: #fff;
+	
+	.feedback-input {
+		width: 100%;
+		height: 300rpx;
+		padding: 20rpx;
+		font-size: 28rpx;
+		color: #333;
+		background: #f8f8f8;
+		border-radius: 12rpx;
+	}
+	
+	.word-count {
+		text-align: right;
+		font-size: 24rpx;
+		color: #999;
+		margin-top: 10rpx;
+	}
+	
+	.image-upload {
+		margin-top: 40rpx;
+		
+		.title {
+			font-size: 28rpx;
+			color: #333;
+			margin-bottom: 20rpx;
+		}
+		
+		.image-list {
+			display: flex;
+			flex-wrap: wrap;
+			
+			.image-item {
+				position: relative;
+				width: 160rpx;
+				height: 160rpx;
+				margin-right: 20rpx;
+				margin-bottom: 20rpx;
+				
+				image {
+					width: 100%;
+					height: 100%;
+					border-radius: 8rpx;
+				}
+				
+				.delete-btn {
+					position: absolute;
+					right: -10rpx;
+					top: -10rpx;
+					width: 40rpx;
+					height: 40rpx;
+					line-height: 40rpx;
+					text-align: center;
+					background: rgba(0, 0, 0, 0.5);
+					color: #fff;
+					border-radius: 50%;
+				}
+			}
+			
+			.upload-btn {
+				width: 160rpx;
+				height: 160rpx;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				background: #f8f8f8;
+				border-radius: 8rpx;
+				
+				.iconfont {
+					font-size: 48rpx;
+					color: #999;
+					margin-bottom: 10rpx;
+				}
+				
+				text {
+					font-size: 24rpx;
+					color: #999;
+				}
+			}
+		}
+	}
+	
+	.contact-input {
+		margin-top: 40rpx;
+		
+		input {
+			width: 100%;
+			height: 88rpx;
+			padding: 0 20rpx;
+			font-size: 28rpx;
+			color: #333;
+			background: #f8f8f8;
+			border-radius: 12rpx;
+		}
+	}
+	
+	.submit-btn {
+		width: 100%;
+		height: 88rpx;
+		line-height: 88rpx;
+		text-align: center;
+		background: #D93025;
+		color: #fff;
+		font-size: 32rpx;
+		border-radius: 44rpx;
+		margin-top: 60rpx;
+	}
+}
+</style> 

+ 106 - 0
packageUser/pages/language/index.vue

@@ -0,0 +1,106 @@
+<template>
+	<view class="language-page">
+		<view class="header">
+			<text class="title">语言设置</text>
+		</view>
+		
+		<view class="language-list">
+			<view class="language-item" 
+				v-for="(item, index) in languages" 
+				:key="index"
+				@tap="selectLanguage(item.value)"
+			>
+				<text>{{item.label}}</text>
+				<text class="iconfont check" v-if="currentLang === item.value">&#xe685;</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			currentLang: 'zh-CN',
+			languages: [
+				{ label: '简体中文', value: 'zh-CN' },
+				{ label: '繁體中文', value: 'zh-TW' },
+				{ label: 'English', value: 'en-US' }
+			]
+		}
+	},
+	onLoad() {
+		// 获取当前语言设置
+		const lang = uni.getStorageSync('language') || 'zh-CN';
+		this.currentLang = lang;
+	},
+	methods: {
+		selectLanguage(lang) {
+			if (lang === this.currentLang) return;
+			
+			uni.showModal({
+				title: '提示',
+				content: '切换语言需要重启应用,是否继续?',
+				success: (res) => {
+					if (res.confirm) {
+						// 保存语言设置
+						uni.setStorageSync('language', lang);
+						this.currentLang = lang;
+						
+						// 重启应用
+						setTimeout(() => {
+							uni.reLaunch({
+								url: '/pages/index/indexNew'
+							});
+						}, 1500);
+					}
+				}
+			});
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.language-page {
+	min-height: 100vh;
+	background: #f5f5f5;
+}
+
+.header {
+	background: #fff;
+	padding: 20rpx 30rpx;
+	
+	.title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.language-list {
+	margin-top: 20rpx;
+	background: #fff;
+	
+	.language-item {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 30rpx;
+		border-bottom: 1px solid #eee;
+		
+		text {
+			font-size: 28rpx;
+			color: #333;
+		}
+		
+		.check {
+			color: #D93025;
+		}
+		
+		&:last-child {
+			border-bottom: none;
+		}
+	}
+}
+</style> 

+ 570 - 0
packageUser/pages/level/index.vue

@@ -0,0 +1,570 @@
+<template>
+	<view class="level-center">
+		<!-- 用户当前等级信息 -->
+		<view class="user-level-info">
+			<view class="level-header">
+				<view class="level-left">
+					<view class="level-title">{{currentLevel.name}}</view>
+					<view class="level-desc">距离下一个等级 <text class="next-level">{{nextLevel.name}}</text></view>
+				</view>
+				<view class="level-right" @click="goToOrderHistory">
+					<view class="order-history">
+						<u-icon name="clock" size="40" color="#fff"></u-icon>
+						<text>购买记录</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 会员权益卡片 -->
+
+		</view>
+
+		<!-- 等级进度条 -->
+		<view class="level-progress">
+			<view class="progress-header">
+				<text class="progress-title">升级进度</text>
+				<text class="progress-percent">{{progressPercentage}}%</text>
+			</view>
+			<view class="progress-bar">
+				<view class="progress-inner" :style="{width: progressPercentage + '%'}">
+					<view class="progress-dot"></view>
+				</view>
+			</view>
+			<view class="level-points">
+				<view class="current-points">
+					<text class="point-label">已购买商品</text>
+					<text class="point-value">{{currentBuyCount}}个</text>
+				</view>
+				<view class="next-level-points">
+					<text class="point-label">还需购买</text>
+					<text class="point-value highlight">{{remainingCount}}个</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 等级图标展示 -->
+		<view class="level-icons">
+			<view v-for="(level, index) in levels" :key="index" class="level-icon-item"
+				:class="{'active': currentLevelIndex >= index}">
+				<view class="level-icon-wrapper" :class="[level.effect]">
+					<image :src="level.icon" mode="aspectFit" class="level-icon"
+						:style="level.color ? {filter: 'drop-shadow(0 0 3px '+level.color+') brightness(1.1)', fill: level.color} : ''">
+					</image>
+				</view>
+				<text class="level-name" :style="level.color ? {color: level.color} : ''">{{level.name}}</text>
+			</view>
+		</view>
+
+		<!-- 等级说明 -->
+		<view class="level-description">
+			<view class="desc-header">
+				<text class="desc-title">等级说明</text>
+				<text class="desc-subtitle">会员等级越高 优惠越多</text>
+			</view>
+			
+			<!-- 等级计算规则说明 -->
+			<view class="level-rule-notice">
+				<view class="rule-icon">
+					<u-icon name="info-circle" size="32" color="#8B5CF6"></u-icon>
+				</view>
+				<view class="rule-content">
+					<view class="rule-title">等级计算规则</view>
+					<view class="rule-text">您的会员等级将在每月1号根据上个月的购买商品数量进行更新,购买数量越多等级越高</view>
+				</view>
+			</view>
+			
+			<view class="desc-list">
+				<view class="desc-item" v-for="(level, index) in levels" :key="index">
+					<view class="desc-left">
+						<view class="desc-level">{{level.name}}</view>
+						<view class="desc-benefits">{{level.benefits}}</view>
+					</view>
+					<view class="desc-right">
+						<view class="desc-requirement">购买商品数达到 <text class="highlight">{{level.requirement}}</text> 个
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		countPaidProducts
+	} from '@/config/api';
+	export default {
+		data() {
+			return {
+				currentBuyCount: 0, // 当前购买数量,从接口获取
+				levels: [{
+						name: '普通会员',
+						requirement: 0,
+						icon: '/static/images/level.png',
+						color: '',
+						effect: ''
+					},
+					{
+						name: 'S级会员',
+						requirement: 20,
+						icon: '/static/images/level1.png',
+						color: '#FFB800',
+						effect: 'level-effect-s'
+					},
+					{
+						name: 'SS级会员',
+						requirement: 40,
+						icon: '/static/images/level2.png',
+						color: '#FF6B6B',
+						effect: 'level-effect-ss'
+					},
+					{
+						name: 'SSS级会员',
+						requirement: 55,
+						icon: '/static/images/level3.png',
+						color: '#8B5CF6',
+						effect: 'level-effect-sss'
+					}
+				]
+			}
+		},
+		computed: {
+			currentLevelIndex() {
+				for (let i = this.levels.length - 1; i >= 0; i--) {
+					if (this.currentBuyCount >= this.levels[i].requirement) {
+						return i;
+					}
+				}
+				return 0;
+			},
+			currentLevel() {
+				return this.levels[this.currentLevelIndex];
+			},
+			nextLevel() {
+				return this.levels[Math.min(this.currentLevelIndex + 1, this.levels.length - 1)];
+			},
+			remainingCount() {
+				if (this.currentLevelIndex === this.levels.length - 1) {
+					return 0;
+				}
+				return this.nextLevel.requirement - this.currentBuyCount;
+			},
+			progressPercentage() {
+				if (this.currentLevelIndex === this.levels.length - 1) {
+					return 100;
+				}
+				const currentRange = this.nextLevel.requirement - this.currentLevel.requirement;
+				const currentProgress = this.currentBuyCount - this.currentLevel.requirement;
+				return Math.min(Math.floor((currentProgress / currentRange) * 100), 100);
+			}
+		},
+		onLoad() {
+			this.getCurrentBuyCount();
+		},
+		methods: {
+			// 获取当前用户购买商品数量
+			async getCurrentBuyCount() {
+				try {
+					const res = await countPaidProducts();
+					if (res.code === 200 && res.data !== null) {
+						this.currentBuyCount = res.data.productCount;
+					}
+				} catch (error) {
+					console.error('获取购买商品数量失败:', error);
+					// 如果接口调用失败,保持默认值
+				}
+			},
+			goToOrderHistory() {
+				uni.navigateTo({
+					url: '/packageOrder/pages/list/index'
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.level-center {
+		padding: 20rpx;
+		background-color: #f5f5f5;
+		min-height: 100vh;
+
+		.user-level-info {
+			background: linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%);
+			padding: 30rpx;
+			border-radius: 20rpx;
+			color: #fff;
+			margin-bottom: 30rpx;
+			box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.2);
+
+			.level-header {
+				display: flex;
+				justify-content: space-between;
+				align-items: flex-start;
+				margin-bottom: 30rpx;
+
+				.level-left {
+					.level-title {
+						font-size: 44rpx;
+						font-weight: bold;
+						margin-bottom: 10rpx;
+					}
+
+					.level-desc {
+						font-size: 28rpx;
+						opacity: 0.9;
+
+						.next-level {
+							color: #FFD700;
+							font-weight: bold;
+						}
+					}
+				}
+
+				.level-right {
+					.order-history {
+						display: flex;
+						flex-direction: column;
+						align-items: center;
+						background: rgba(255, 255, 255, 0.1);
+						padding: 10rpx 20rpx;
+						border-radius: 12rpx;
+
+						text {
+							font-size: 24rpx;
+							margin-top: 6rpx;
+						}
+					}
+				}
+			}
+
+			.level-benefits {
+				display: flex;
+				justify-content: space-around;
+				margin-top: 20rpx;
+				background: rgba(255, 255, 255, 0.1);
+				border-radius: 16rpx;
+				padding: 20rpx 0;
+
+				.benefit-item {
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+
+					.benefit-value {
+						font-size: 32rpx;
+						font-weight: bold;
+						margin-bottom: 6rpx;
+					}
+
+					.benefit-label {
+						font-size: 24rpx;
+						opacity: 0.8;
+					}
+				}
+			}
+		}
+
+		.level-progress {
+			background-color: #fff;
+			padding: 30rpx;
+			border-radius: 20rpx;
+			margin-bottom: 30rpx;
+			box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+			.progress-header {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				margin-bottom: 20rpx;
+
+				.progress-title {
+					font-size: 28rpx;
+					color: #333;
+					font-weight: bold;
+				}
+
+				.progress-percent {
+					font-size: 28rpx;
+					color: #8B5CF6;
+					font-weight: bold;
+				}
+			}
+
+			.progress-bar {
+				height: 16rpx;
+				background-color: #E5E7EB;
+				border-radius: 8rpx;
+				overflow: hidden;
+				margin-bottom: 20rpx;
+				position: relative;
+
+				.progress-inner {
+					height: 100%;
+					background: linear-gradient(to right, #8B5CF6, #6366F1);
+					border-radius: 8rpx;
+					transition: width 0.3s;
+					position: relative;
+
+					.progress-dot {
+						position: absolute;
+						right: -6rpx;
+						top: 50%;
+						transform: translateY(-50%);
+						width: 12rpx;
+						height: 12rpx;
+						background: #fff;
+						border-radius: 50%;
+						box-shadow: 0 0 10rpx rgba(139, 92, 246, 0.5);
+					}
+				}
+			}
+
+			.level-points {
+				display: flex;
+				justify-content: space-between;
+				font-size: 24rpx;
+
+				.current-points,
+				.next-level-points {
+					display: flex;
+					flex-direction: column;
+					align-items: flex-start;
+
+					.point-label {
+						color: #666;
+						margin-bottom: 4rpx;
+					}
+
+					.point-value {
+						font-size: 28rpx;
+						font-weight: bold;
+						color: #333;
+
+						&.highlight {
+							color: #FF6B6B;
+						}
+					}
+				}
+			}
+		}
+
+		.level-icons {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			background-color: #fff;
+			padding: 30rpx;
+			border-radius: 20rpx;
+			margin-bottom: 30rpx;
+
+			.level-icon-item {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				opacity: 0.5;
+
+				&.active {
+					opacity: 1;
+				}
+
+				.level-icon-wrapper {
+					width: 90rpx;
+					height: 90rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					margin-bottom: 10rpx;
+					position: relative;
+
+					&.level-effect-s {
+						&::before {
+							content: '';
+							position: absolute;
+							width: 110%;
+							height: 110%;
+							border-radius: 50%;
+							border: 2rpx solid rgba(255, 184, 0, 0.3);
+							animation: borderPulse 1.5s ease-in-out infinite;
+						}
+					}
+
+					&.level-effect-ss {
+						&::before {
+							content: '';
+							position: absolute;
+							width: 115%;
+							height: 115%;
+							border-radius: 50%;
+							background: linear-gradient(45deg,
+									rgba(255, 107, 107, 0.1) 0%,
+									rgba(255, 107, 107, 0.2) 50%,
+									rgba(255, 107, 107, 0.1) 100%);
+							animation: shine 2s linear infinite;
+						}
+					}
+
+					&.level-effect-sss {
+						&::before {
+							content: '';
+							position: absolute;
+							width: 120%;
+							height: 120%;
+							border-radius: 50%;
+							background: linear-gradient(135deg,
+									rgba(139, 92, 246, 0.1) 0%,
+									rgba(139, 92, 246, 0.2) 50%,
+									rgba(139, 92, 246, 0.1) 100%);
+							animation: shine 1.5s linear infinite;
+						}
+					}
+
+					.level-icon {
+						width: 70rpx;
+						height: 70rpx;
+						position: relative;
+						z-index: 1;
+					}
+				}
+
+				@keyframes borderPulse {
+					0% {
+						transform: scale(1);
+						opacity: 0.6;
+					}
+
+					50% {
+						transform: scale(1.1);
+						opacity: 0.3;
+					}
+
+					100% {
+						transform: scale(1);
+						opacity: 0.6;
+					}
+				}
+
+				@keyframes shine {
+					0% {
+						transform: rotate(0deg);
+						opacity: 0.3;
+					}
+
+					50% {
+						opacity: 0.5;
+					}
+
+					100% {
+						transform: rotate(360deg);
+						opacity: 0.3;
+					}
+				}
+
+				.level-name {
+					font-size: 24rpx;
+					color: #666;
+				}
+			}
+		}
+
+		.level-description {
+			background-color: #fff;
+			padding: 30rpx;
+			border-radius: 20rpx;
+			box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+			.level-rule-notice {
+				display: flex;
+				align-items: flex-start;
+				background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(99, 102, 241, 0.05) 100%);
+				border: 2rpx solid rgba(139, 92, 246, 0.1);
+				border-radius: 16rpx;
+				padding: 24rpx;
+				margin-bottom: 30rpx;
+
+				.rule-icon {
+					margin-right: 16rpx;
+					margin-top: 4rpx;
+					flex-shrink: 0;
+				}
+
+				.rule-content {
+					flex: 1;
+
+					.rule-title {
+						font-size: 28rpx;
+						font-weight: bold;
+						color: #8B5CF6;
+						margin-bottom: 8rpx;
+					}
+
+					.rule-text {
+						font-size: 26rpx;
+						color: #666;
+						line-height: 1.5;
+					}
+				}
+			}
+
+			.desc-header {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				margin-bottom: 30rpx;
+				padding-bottom: 20rpx;
+				border-bottom: 2rpx solid #f5f5f5;
+
+				.desc-title {
+					font-size: 32rpx;
+					font-weight: bold;
+					color: #333;
+				}
+
+				.desc-subtitle {
+					font-size: 24rpx;
+					color: #666;
+				}
+			}
+
+			.desc-list {
+				.desc-item {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					padding: 24rpx 0;
+					border-bottom: 1px solid #f5f5f5;
+
+					&:last-child {
+						border-bottom: none;
+					}
+
+					.desc-left {
+						.desc-level {
+							font-size: 28rpx;
+							color: #333;
+							font-weight: bold;
+							margin-bottom: 6rpx;
+						}
+
+						.desc-benefits {
+							font-size: 24rpx;
+							color: #666;
+						}
+					}
+
+					.desc-right {
+						.desc-requirement {
+							font-size: 26rpx;
+							color: #666;
+
+							.highlight {
+								color: #8B5CF6;
+								font-weight: bold;
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+</style>

+ 923 - 0
packageUser/pages/login/index.vue

@@ -0,0 +1,923 @@
+<template>
+	<view class="login-page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar"></view>
+
+		<!-- 水墨背景 -->
+		<view class="ink-bg">
+			<view class="ink-wash ink-1"></view>
+			<view class="ink-wash ink-2"></view>
+			<view class="ink-wash ink-3"></view>
+		</view>
+
+		<!-- 返回按钮 -->
+		<view class="nav-back pad-t-20" @click="goBack">
+			<view class="back-btn">
+				<u-icon size="30" name="arrow-left"></u-icon>
+			</view>
+		</view>
+
+		<!-- Logo和标题 -->
+		<view class="header">
+			<view class="logo-container">
+				<view class="logo-circle">
+					<image class="logo"
+						src="https://ndtk.tos-cn-guangzhou.volces.com/uploads/156adea827104e38ae0e25ec4701ecfe.jpg"
+						mode="aspectFit"></image>
+				</view>
+				<view class="logo-shadow"></view>
+			</view>
+			<view class="title-section">
+				<text class="title">宏匠</text>
+				<view class="title-brush"></view>
+			</view>
+			<text class="subtitle">唐卡世界,一键开启</text>
+		</view>
+
+		<!-- 登录按钮 -->
+		<view class="login-section">
+			<view class="btn-container">
+				<button class="wechat-btn" @tap="handleWxLogin" :class="{ 'btn-loading': isLoading }">
+					<view class="btn-ink"></view>
+					<text class="font32">一键快捷登录</text>
+				</button>
+			</view>
+
+			<view class="zen-divider">
+				<view class="zen-dot"></view>
+				<view class="zen-line"></view>
+				<view class="zen-dot"></view>
+			</view>
+
+			<view class="btn-container">
+				<button class="browse-btn" @tap="handleBrowse">
+					<text>暂不登录,先去逛逛</text>
+				</button>
+			</view>
+
+			<view class="agreement">
+				<view class="checkbox-wrapper" @tap="toggleAgreement">
+					<view class="zen-checkbox" :class="{ 'checked': isAgree }">
+						<view class="zen-circle-inner" v-if="isAgree"></view>
+					</view>
+				</view>
+				<text class="agreement-text">
+					登录即代表您已同意
+					<text class="link" @tap="goToPrivacy">《隐私政策》</text>
+					和
+					<text class="link" @tap="goToService">《服务协议》</text>
+				</text>
+			</view>
+		</view>
+
+		<!-- 底部装饰 -->
+		<view class="bottom-decoration">
+			<view class="zen-mountain">
+				<view class="mountain-peak"></view>
+				<view class="mountain-base"></view>
+			</view>
+		</view>
+
+		<!-- 首次登录设置弹框 -->
+		<u-popup :show="showProfileModal">
+			<view class="profile-modal">
+				<view class="modal-header">
+					<text class="modal-title">完善个人信息</text>
+					<text class="modal-subtitle">请设置您的账号和头像</text>
+				</view>
+
+				<view class="modal-content">
+					<!-- 头像选择 -->
+					<view class="avatar-section">
+						<text class="section-label">头像</text>
+						<button class="avatar-button" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"
+							:disabled="isUploadingAvatar">
+							<view class="avatar-container">
+								<image class="avatar-preview" :src="tempAvatar || '/static/images/avatar.png'"
+									mode="aspectFill"></image>
+								<view class="avatar-overlay">
+									<u-icon v-if="!isUploadingAvatar" name="camera" size="24" color="#fff"></u-icon>
+									<u-loading-icon v-else mode="spinner" size="24" color="#fff"></u-loading-icon>
+								</view>
+							</view>
+						</button>
+					</view>
+
+					<!-- 账号输入 -->
+					<view class="account-section">
+						<text class="section-label">用户名</text>
+						<u-input v-model="tempAccount" placeholder="请输入用户名" :maxlength="20" :clearable="true"
+							border="surround"></u-input>
+					</view>
+				</view>
+
+				<view class="modal-actions">
+					<button class="confirm-btn" @tap="confirmProfile"
+						:disabled="!tempAccount.trim() || isUploadingAvatar">
+						<text v-if="!isUploadingAvatar">确认</text>
+						<text v-else>头像上传中...</text>
+					</button>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		wxLogin,
+		updateProfile
+	} from '@/config/api.js';
+	import {
+		UPLOAD_URL
+	} from '@/common/config.js';
+
+	export default {
+		data() {
+			return {
+				isAgree: false,
+				isLoading: false,
+				redirectUrl: '', // 登录成功后的重定向地址
+				showProfileModal: false, // 显示个人信息设置弹框
+				tempAccount: '', // 临时账号
+				tempAvatar: '', // 临时头像
+				loginData: null, // 临时保存登录数据
+				isUploadingAvatar: false // 头像上传状态
+			}
+		},
+		onLoad(options) {
+			// 获取重定向地址
+			if (options.redirect) {
+				this.redirectUrl = decodeURIComponent(options.redirect);
+			}
+		},
+		methods: {
+			// 处理微信登录
+			async handleWxLogin() {
+				if (!this.isAgree) {
+					let that = this
+					// 弹出确认弹框询问用户是否同意协议
+					uni.showModal({
+						title: '提示',
+						content: '登录需要您同意《隐私政策》和《服务协议》,是否同意?',
+						success: function(res) {
+							if (res.confirm) {
+								// 用户点击同意,自动勾选协议并执行登录
+								that.isAgree = true;
+								that.executeLogin();
+							} else if (res.cancel) {
+								console.log('用户点击取消');
+							}
+						}
+					});
+
+					return;
+				}
+
+				this.executeLogin();
+			},
+
+			// 执行登录逻辑
+			async executeLogin() {
+				uni.showLoading({
+					mask: true
+				})
+				this.isLoading = true;
+
+				uni.getUserProfile({
+					desc: '用于完善会员资料',
+					success: (profileRes) => {
+						uni.login({
+							provider: 'weixin',
+							success: async (loginRes) => {
+								try {
+									const loginData = await wxLogin({
+										code: loginRes.code,
+										account: profileRes.userInfo.nickName,
+										avatarUrl: profileRes.userInfo.avatarUrl,
+										grant_type: 'wechat'
+									});
+
+									if (loginData.code === 200) {
+										// 检查是否为首次登录
+										if (loginData.isFirstLogin) {
+											// 首次登录,显示个人信息设置弹框
+											this.loginData = loginData;
+											this.tempAccount = loginData.account || '';
+											this.tempAvatar = loginData.avatar || '';
+											this.showProfileModal = true;
+											this.isLoading = false;
+											uni.setStorageSync('access_token', loginData
+												.access_token);
+											uni.setStorageSync('refresh_token', loginData
+												.refresh_token);
+											uni.setStorageSync('user', loginData);
+
+											this.$store.commit('isLogin', true);
+											this.$store.commit('refresh_token', loginData
+												.refresh_token);
+											uni.hideLoading();
+										} else {
+											// 非首次登录,直接保存并跳转
+											this.saveLoginDataAndRedirect(loginData);
+										}
+									} else {
+										uni.showToast({
+											title: loginData.msg || '登录失败',
+											icon: 'none'
+										});
+									}
+								} catch (e) {
+									console.error('登录错误:', e);
+									uni.showToast({
+										title: '登录失败,请重试',
+										icon: 'none'
+									});
+								} finally {
+									this.isLoading = false;
+								}
+							},
+							fail: () => {
+								this.isLoading = false;
+							}
+						});
+					},
+					fail: (err) => {
+						this.isLoading = false;
+						if (err.errMsg.includes('cancel')) {
+							return; // 用户取消,不显示提示
+						}
+						uni.showToast({
+							title: '获取用户信息失败',
+							icon: 'none'
+						});
+					}
+				});
+			},
+
+			// 切换协议同意状态
+			toggleAgreement() {
+				this.isAgree = !this.isAgree;
+			},
+
+			// 返回上一页
+			goBack() {
+				uni.navigateBack();
+			},
+
+			// 跳转到隐私政策
+			goToPrivacy() {
+				uni.navigateTo({
+					url: '/packageUser/pages/richtext/index'
+				});
+			},
+
+			// 跳转到服务协议
+			goToService() {
+				uni.navigateTo({
+					url: '/packageUser/pages/richtext/index'
+				});
+			},
+
+			handleBrowse() {
+				// 直接返回首页
+				uni.switchTab({
+					url: '/pages/index/indexNew'
+				});
+			},
+
+			// 保存登录数据并跳转
+			saveLoginDataAndRedirect(loginData) {
+				uni.setStorageSync('access_token', loginData.access_token);
+				uni.setStorageSync('refresh_token', loginData.refresh_token);
+				uni.setStorageSync('user', loginData);
+
+				this.$store.commit('isLogin', true);
+				this.$store.commit('refresh_token', loginData.refresh_token);
+
+				uni.showToast({
+					title: '登录成功',
+					icon: 'success',
+					duration: 1000
+				});
+
+				// 登录成功后的跳转
+				setTimeout(() => {
+					if (this.redirectUrl) {
+						uni.redirectTo({
+							url: this.redirectUrl
+						});
+					} else {
+						// 检查是否有待处理的邀请,如果有则跳转到首页触发邀请弹框
+						const pendingInvitation = uni.getStorageSync('pendingInvitation');
+						if (pendingInvitation && pendingInvitation.inviterId) {
+							console.log('登录成功,发现待处理邀请,跳转到首页');
+							// 使用 reLaunch 确保首页能重新加载并检查邀请
+							uni.reLaunch({
+								url: '/pages/index/indexNew'
+							});
+						} else {
+							uni.switchTab({
+								url: '/pages/index/indexNew'
+							});
+						}
+					}
+				}, 1000);
+			},
+
+			// 微信小程序头像选择回调
+			async onChooseAvatar(e) {
+				console.log('选择头像:', e.detail.avatarUrl);
+
+				// 显示上传状态
+				this.isUploadingAvatar = true;
+				uni.showLoading({
+					title: '上传头像中...',
+					mask: true
+				});
+
+				try {
+					// 上传头像到服务器
+					const uploadResult = await this.uploadFilePromise(e.detail.avatarUrl);
+					if (uploadResult && uploadResult.data && uploadResult.data.link) {
+						this.tempAvatar = uploadResult.data.link;
+						uni.showToast({
+							title: '头像上传成功',
+							icon: 'success'
+						});
+					} else {
+						throw new Error('上传失败');
+					}
+				} catch (error) {
+					console.error('头像上传失败:', error);
+					uni.showToast({
+						title: '头像上传失败,请重试',
+						icon: 'none'
+					});
+				} finally {
+					this.isUploadingAvatar = false;
+					uni.hideLoading();
+				}
+			},
+
+			// 上传文件到服务器
+			uploadFilePromise(filePath) {
+				return new Promise((resolve, reject) => {
+					// 优先使用登录数据中的token,如果没有则使用本地存储的token
+					const token = (this.loginData && this.loginData.access_token) || uni.getStorageSync(
+						'access_token');
+
+					if (!token) {
+						reject(new Error('未找到访问令牌'));
+						return;
+					}
+
+					uni.uploadFile({
+						url: UPLOAD_URL,
+						filePath: filePath,
+						header: {
+							"Blade-Auth": token
+						},
+						name: 'file',
+						formData: {
+							user: 'avatar'
+						},
+						success: (res) => {
+							try {
+								const result = JSON.parse(res.data);
+								if (result.success || result.code === 200) {
+									resolve(result);
+								} else {
+									reject(new Error(result.msg || '上传失败'));
+								}
+							} catch (e) {
+								reject(new Error('解析响应失败'));
+							}
+						},
+						fail: (error) => {
+							reject(error);
+						}
+					});
+				});
+			},
+
+			// 确认个人信息设置
+			async confirmProfile() {
+				if (!this.tempAccount.trim()) {
+					uni.showToast({
+						title: '请输入账号',
+						icon: 'none'
+					});
+					return;
+				}
+
+				uni.showLoading({
+					title: '保存中...',
+					mask: true
+				});
+
+				try {
+					// 调用更新用户信息接口
+					const updateRes = await updateProfile({
+						account: this.tempAccount.trim(),
+						avatar: this.tempAvatar
+					});
+
+					if (updateRes.code == 200) {
+						// 更新登录数据中的用户信息
+						this.loginData.account = this.tempAccount.trim();
+						if (this.tempAvatar) {
+							this.loginData.avatar = this.tempAvatar;
+							this.loginData.avatarUrl = this.tempAvatar; // 同时更新 avatarUrl 字段
+						}
+
+						// 更新本地存储的用户信息
+						uni.setStorageSync('user', this.loginData);
+
+						// 保存登录数据并跳转
+						this.saveLoginDataAndRedirect(this.loginData);
+						this.showProfileModal = false;
+					} else {
+						uni.showToast({
+							title: updateRes.msg || '保存失败,请重试',
+							icon: 'none'
+						});
+					}
+				} catch (error) {
+					console.error('更新用户信息错误:', error);
+					uni.showToast({
+						title: '保存失败,请重试',
+						icon: 'none'
+					});
+				} finally {
+					uni.hideLoading();
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.login-page {
+		min-height: 100vh;
+		background: linear-gradient(135deg, #f8f6f0 0%, #e8e4d8 50%, #d4cfc0 100%);
+		position: relative;
+		overflow: hidden;
+	}
+
+	.status-bar {
+		height: var(--status-bar-height);
+		width: 100%;
+	}
+
+	// 水墨背景
+	.ink-bg {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		pointer-events: none;
+		z-index: 1;
+
+		.ink-wash {
+			position: absolute;
+			border-radius: 50%;
+			background: radial-gradient(circle, rgba(0, 0, 0, 0.03) 0%, transparent 70%);
+
+			&.ink-1 {
+				width: 400rpx;
+				height: 400rpx;
+				top: 10%;
+				right: -100rpx;
+				transform: rotate(15deg);
+			}
+
+			&.ink-2 {
+				width: 300rpx;
+				height: 300rpx;
+				top: 40%;
+				left: -50rpx;
+				transform: rotate(-20deg);
+			}
+
+			&.ink-3 {
+				width: 200rpx;
+				height: 200rpx;
+				bottom: 20%;
+				right: 20%;
+				transform: rotate(45deg);
+			}
+		}
+	}
+
+	.nav-back {
+		position: fixed;
+		left: 30rpx;
+		top: calc(var(--status-bar-height) + 20rpx);
+		z-index: 100;
+
+		.back-btn {
+			width: 80rpx;
+			height: 80rpx;
+			background: rgba(255, 255, 255, 0.8);
+			border: 1rpx solid rgba(0, 0, 0, 0.1);
+			border-radius: 50%;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			transition: all 0.3s ease;
+			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+
+			&:active {
+				transform: scale(0.95);
+				box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
+			}
+		}
+
+		.back-icon {
+			font-family: "iconfont";
+			font-size: 36rpx;
+			color: #333;
+		}
+	}
+
+	.header {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding-top: 120rpx;
+		position: relative;
+		z-index: 2;
+
+		.logo-container {
+			position: relative;
+			margin-bottom: 60rpx;
+
+			.logo-circle {
+				width: 200rpx;
+				height: 200rpx;
+				border-radius: 50%;
+				background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+				border: 2rpx solid rgba(0, 0, 0, 0.05);
+
+				.logo {
+					width: 160rpx;
+					height: 160rpx;
+					border-radius: 50%;
+				}
+			}
+
+			.logo-shadow {
+				position: absolute;
+				bottom: -20rpx;
+				left: 50%;
+				transform: translateX(-50%);
+				width: 160rpx;
+				height: 20rpx;
+				background: radial-gradient(ellipse, rgba(0, 0, 0, 0.1) 0%, transparent 70%);
+				border-radius: 50%;
+			}
+		}
+
+		.title-section {
+			text-align: center;
+			margin-bottom: 20rpx;
+			position: relative;
+
+			.title {
+				font-size: 64rpx;
+				font-weight: normal;
+				color: #2c3e50;
+				letter-spacing: 12rpx;
+				font-family: 'KaiTi', '楷体', serif;
+				text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+			}
+
+			.title-brush {
+				position: absolute;
+				bottom: -10rpx;
+				left: 50%;
+				transform: translateX(-50%);
+				width: 120rpx;
+				height: 4rpx;
+				background: linear-gradient(90deg, transparent, #2c3e50, transparent);
+				border-radius: 2rpx;
+			}
+		}
+
+		.subtitle {
+			font-size: 28rpx;
+			color: rgba(44, 62, 80, 0.7);
+			font-weight: 300;
+			letter-spacing: 4rpx;
+			font-style: italic;
+			font-family: inherit;
+		}
+	}
+
+	.login-section {
+		padding: 80rpx 60rpx;
+		position: relative;
+		z-index: 2;
+
+		.btn-container {
+			margin-bottom: 40rpx;
+		}
+
+		.wechat-btn {
+			position: relative;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			width: 100%;
+			height: 96rpx;
+			background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
+			border: 2rpx solid rgba(0, 0, 0, 0.08);
+			border-radius: 48rpx;
+			color: #2c3e50;
+			font-size: 32rpx;
+			font-weight: normal;
+			transition: all 0.3s ease;
+			box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+			overflow: hidden;
+
+			&:active {
+				transform: translateY(2rpx);
+				box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
+			}
+
+			&.btn-loading {
+				background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
+				color: rgba(44, 62, 80, 0.5);
+			}
+
+			.btn-ink {
+				position: absolute;
+				top: 0;
+				left: 0;
+				width: 100%;
+				height: 100%;
+				background: linear-gradient(135deg, rgba(0, 0, 0, 0.02) 0%, transparent 50%);
+				pointer-events: none;
+			}
+
+			.wechat-icon {
+				width: 44rpx;
+				height: 44rpx;
+				margin-right: 16rpx;
+			}
+		}
+
+		.zen-divider {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin: 50rpx 0;
+
+			.zen-dot {
+				width: 8rpx;
+				height: 8rpx;
+				border-radius: 50%;
+				background: rgba(44, 62, 80, 0.3);
+			}
+
+			.zen-line {
+				width: 100rpx;
+				height: 1rpx;
+				background: linear-gradient(90deg, transparent, rgba(44, 62, 80, 0.2), transparent);
+				margin: 0 20rpx;
+			}
+		}
+
+		.browse-btn {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			width: 100%;
+			height: 88rpx;
+			background: transparent;
+			border: 1rpx solid rgba(44, 62, 80, 0.15);
+			border-radius: 44rpx;
+			color: rgba(44, 62, 80, 0.7);
+			font-size: 28rpx;
+			transition: all 0.3s ease;
+
+			&:active {
+				background: rgba(44, 62, 80, 0.05);
+				transform: translateY(1rpx);
+			}
+		}
+
+		.agreement {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin-top: 60rpx;
+
+			.checkbox-wrapper {
+				margin-right: 16rpx;
+			}
+
+			.zen-checkbox {
+				width: 36rpx;
+				height: 36rpx;
+				border: 2rpx solid rgba(44, 62, 80, 0.3);
+				border-radius: 50%;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				transition: all 0.3s ease;
+				background: rgba(255, 255, 255, 0.8);
+
+				&.checked {
+					background: #2c3e50;
+					border-color: #2c3e50;
+				}
+
+				.zen-circle-inner {
+					width: 16rpx;
+					height: 16rpx;
+					border-radius: 50%;
+					background: #fff;
+				}
+			}
+
+			.agreement-text {
+				font-size: 24rpx;
+				color: rgba(44, 62, 80, 0.6);
+				line-height: 1.5;
+
+				.link {
+					color: #2c3e50;
+					text-decoration: underline;
+				}
+			}
+		}
+	}
+
+	// 底部装饰
+	.bottom-decoration {
+		position: absolute;
+		bottom: 40rpx;
+		left: 50%;
+		transform: translateX(-50%);
+		z-index: 1;
+
+		.zen-mountain {
+			position: relative;
+			width: 200rpx;
+			height: 120rpx;
+
+			.mountain-peak {
+				position: absolute;
+				top: 0;
+				left: 50%;
+				transform: translateX(-50%);
+				width: 0;
+				height: 0;
+				border-left: 60rpx solid transparent;
+				border-right: 60rpx solid transparent;
+				border-bottom: 80rpx solid rgba(44, 62, 80, 0.1);
+			}
+
+			.mountain-base {
+				position: absolute;
+				bottom: 0;
+				left: 0;
+				width: 100%;
+				height: 40rpx;
+				background: linear-gradient(90deg, transparent, rgba(44, 62, 80, 0.05), transparent);
+				border-radius: 50%;
+			}
+		}
+	}
+
+	// 个人信息设置弹框样式
+	.profile-modal {
+		width: 750rpx;
+		background: #fff;
+		border-radius: 20rpx;
+		overflow: hidden;
+
+		.modal-header {
+			text-align: center;
+			padding: 60rpx 40rpx 40rpx;
+			background: linear-gradient(135deg, #f8f6f0 0%, #e8e4d8 100%);
+
+			.modal-title {
+				display: block;
+				font-size: 36rpx;
+				font-weight: 600;
+				color: #2c3e50;
+				margin-bottom: 12rpx;
+			}
+
+			.modal-subtitle {
+				font-size: 26rpx;
+				color: rgba(44, 62, 80, 0.7);
+			}
+		}
+
+		.modal-content {
+			padding: 40rpx;
+
+			.avatar-section {
+				margin-bottom: 40rpx;
+				text-align: center;
+
+				.section-label {
+					display: block;
+					font-size: 28rpx;
+					color: #2c3e50;
+					margin-bottom: 20rpx;
+					font-weight: 500;
+				}
+
+				.avatar-button {
+					background: none;
+					border: none;
+					padding: 0;
+					margin: 0 auto;
+					display: block;
+					width: 160rpx;
+					height: 160rpx;
+
+					&::after {
+						border: none;
+					}
+
+					&:disabled {
+						opacity: 0.7;
+					}
+				}
+
+				.avatar-container {
+					position: relative;
+					width: 160rpx;
+					height: 160rpx;
+					border-radius: 50%;
+					overflow: hidden;
+					border: 3rpx solid #e8e4d8;
+
+					.avatar-preview {
+						width: 100%;
+						height: 100%;
+					}
+
+					.avatar-overlay {
+						position: absolute;
+						bottom: 0;
+						left: 0;
+						right: 0;
+						height: 50rpx;
+						background: rgba(0, 0, 0, 0.5);
+						display: flex;
+						align-items: center;
+						justify-content: center;
+					}
+				}
+			}
+
+			.account-section {
+				.section-label {
+					display: block;
+					font-size: 28rpx;
+					color: #2c3e50;
+					margin-bottom: 20rpx;
+					font-weight: 500;
+				}
+			}
+		}
+
+		.modal-actions {
+			padding: 0 40rpx 40rpx;
+
+			.confirm-btn {
+				width: 100%;
+				height: 80rpx;
+				background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
+				color: #fff;
+				border-radius: 40rpx;
+				font-size: 32rpx;
+				font-weight: 500;
+				border: none;
+				transition: all 0.3s ease;
+
+				&:disabled {
+					background: #bdc3c7;
+					color: rgba(255, 255, 255, 0.7);
+				}
+
+				&:not(:disabled):active {
+					transform: translateY(2rpx);
+					box-shadow: 0 2rpx 8rpx rgba(44, 62, 80, 0.3);
+				}
+			}
+		}
+	}
+</style>

+ 260 - 0
packageUser/pages/register/index.vue

@@ -0,0 +1,260 @@
+<template>
+	<view class="register-page">
+		<!-- 状态栏占位 -->
+		<view class="status-bar"></view>
+		
+		<!-- 返回按钮 -->
+		<view class="nav-back" @click="goBack">
+			<text class="back-icon">&#xe685;</text>
+		</view>
+		
+		<!-- 页面标题 -->
+		<view class="header">
+			<text class="title">注册账号</text>
+		</view>
+		
+		<!-- 注册表单 -->
+		<view class="form-section">
+			<view class="form-item">
+				<input type="text" v-model="form.phone" placeholder="请输入手机号" />
+			</view>
+			<view class="form-item code-item">
+				<input type="text" v-model="form.code" placeholder="请输入验证码" />
+				<button class="code-btn" :disabled="counting" @tap="getCode">
+					{{counting ? `${counter}s后重新获取` : '获取验证码'}}
+				</button>
+			</view>
+			<view class="form-item">
+				<input type="password" v-model="form.password" placeholder="请设置密码" />
+			</view>
+			
+			<button class="submit-btn" @tap="handleSubmit">注册</button>
+			
+			<view class="agreement">
+				<checkbox :checked="isAgree" @tap="toggleAgreement"></checkbox>
+				<text class="agreement-text">
+					注册即代表您已同意
+					<text class="link" @tap="goToPrivacy">《隐私政策》</text>
+					和
+					<text class="link" @tap="goToService">《服务协议》</text>
+				</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			form: {
+				phone: '',
+				code: '',
+				password: ''
+			},
+			isAgree: false,
+			counting: false,
+			counter: 60
+		}
+	},
+	methods: {
+		// 返回上一页
+		goBack() {
+			uni.navigateBack();
+		},
+		
+		// 获取验证码
+		getCode() {
+			if (!this.form.phone) {
+				uni.showToast({
+					title: '请输入手机号',
+					icon: 'none'
+				});
+				return;
+			}
+			
+			// 开始倒计时
+			this.counting = true;
+			this.counter = 60;
+			const timer = setInterval(() => {
+				this.counter--;
+				if (this.counter <= 0) {
+					clearInterval(timer);
+					this.counting = false;
+				}
+			}, 1000);
+			
+			// TODO: 调用发送验证码接口
+			uni.showToast({
+				title: '验证码已发送',
+				icon: 'success'
+			});
+		},
+		
+		// 切换协议同意状态
+		toggleAgreement() {
+			this.isAgree = !this.isAgree;
+		},
+		
+		// 提交注册
+		handleSubmit() {
+			if (!this.isAgree) {
+				uni.showToast({
+					title: '请先同意用户协议和隐私政策',
+					icon: 'none'
+				});
+				return;
+			}
+			
+			if (!this.form.phone || !this.form.code || !this.form.password) {
+				uni.showToast({
+					title: '请填写完整信息',
+					icon: 'none'
+				});
+				return;
+			}
+			
+			// TODO: 调用注册接口
+			uni.showLoading({
+				title: '注册中...'
+			});
+			
+			setTimeout(() => {
+				uni.hideLoading();
+				uni.showToast({
+					title: '注册成功',
+					icon: 'success'
+				});
+				
+				// 注册成功后跳转到登录页
+				setTimeout(() => {
+					uni.navigateTo({
+						url: '/pages/user/login'
+					});
+				}, 1500);
+			}, 1000);
+		},
+		
+		// 跳转到隐私政策
+		goToPrivacy() {
+			uni.navigateTo({
+				url: '/packageUser/pages/article/detail?type=privacy'
+			});
+		},
+		
+		// 跳转到服务协议
+		goToService() {
+			uni.navigateTo({
+				url: '/packageUser/pages/article/detail?type=service'
+			});
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.register-page {
+	min-height: 100vh;
+	background: #fff;
+	padding-bottom: env(safe-area-inset-bottom);
+}
+
+.status-bar {
+	height: var(--status-bar-height);
+	width: 100%;
+}
+
+.nav-back {
+	position: fixed;
+	left: 30rpx;
+	top: calc(var(--status-bar-height) + 20rpx);
+	z-index: 100;
+	
+	.back-icon {
+		font-family: "iconfont";
+		font-size: 40rpx;
+		color: #333;
+	}
+}
+
+.header {
+	padding: 60rpx 40rpx;
+	
+	.title {
+		font-size: 48rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.form-section {
+	padding: 0 40rpx;
+	
+	.form-item {
+		border-bottom: 1px solid #eee;
+		padding: 20rpx 0;
+		margin-bottom: 20rpx;
+		
+		input {
+			font-size: 32rpx;
+			color: #333;
+		}
+	}
+	
+	.code-item {
+		display: flex;
+		align-items: center;
+		
+		input {
+			flex: 1;
+		}
+		
+		.code-btn {
+			width: 200rpx;
+			height: 60rpx;
+			line-height: 60rpx;
+			font-size: 24rpx;
+			color: #fff;
+			background: #D93025;
+			border-radius: 30rpx;
+			margin-left: 20rpx;
+			
+			&[disabled] {
+				background: #ccc;
+			}
+		}
+	}
+	
+	.submit-btn {
+		width: 100%;
+		height: 88rpx;
+		line-height: 88rpx;
+		background: #D93025;
+		color: #fff;
+		font-size: 32rpx;
+		border-radius: 44rpx;
+		margin-top: 60rpx;
+	}
+	
+	.agreement {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-top: 40rpx;
+		
+		checkbox {
+			transform: scale(0.7);
+		}
+		
+		.agreement-text {
+			font-size: 24rpx;
+			color: #666;
+			margin-left: 8rpx;
+			
+			.link {
+				color: #D93025;
+			}
+		}
+	}
+}
+</style> 

+ 93 - 0
packageUser/pages/richtext/index.vue

@@ -0,0 +1,93 @@
+<!-- 富文本 -->
+<template>
+	<view class="bg-FFF pad-24">
+		<text>
+			宏匠唐卡用户协议​
+			欢迎您使用清远市宏匠贸易有限公司(以下简称 “我们”)提供的 “宏匠唐卡” 小程序(以下简称 “本服务”)。为了保障您的合法权益,请您仔细阅读并同意本《用户协议》(以下简称 “本协议”)。​
+			一、定义​
+			用户:指同意本协议并使用本服务的个人或组织。​
+			服务:指我们通过 “宏匠唐卡” 小程序向用户提供的各项功能及服务。​
+			二、服务内容与使用规则​
+			服务内容:我们为用户提供项目管理相关的功能,包括但不限于项目创建、任务分配、进度跟踪、文档管理等。具体服务内容以小程序实际提供的为准。​
+			使用规则​
+			用户需遵守法律法规及社会公德,不得利用本服务从事违法违规或侵犯他人合法权益的活动。​
+			用户应妥善保管自己的账号及密码,因用户原因导致账号被盗用或信息泄露的,用户应自行承担相应后果。​
+			未经我们书面同意,用户不得对本服务进行反向工程、反向编译、修改或破解。​
+			三、账号注册与管理​
+			注册:用户注册账号时需提供真实、准确、完整的信息。如因用户提供虚假信息导致的一切后果,由用户自行承担。​
+			管理:我们有权对用户账号进行管理,包括但不限于冻结、注销违反本协议的账号。​
+			四、知识产权​
+			本服务及相关内容(包括但不限于文字、图片、软件等)的知识产权归我们所有。用户仅有权在本协议约定的范围内使用本服务。​
+			用户在使用本服务过程中上传的内容,其知识产权归用户所有,但用户授予我们在全球范围内、永久、免费、可再许可的权利,以使用、复制、修改、发布、展示、传播这些内容。​
+			五、责任限制与免责​
+			责任限制:在法律允许的最大范围内,我们对因本服务引起的任何直接、间接、偶然、特殊及后果性的损害不承担责任。​
+			免责:因不可抗力、第三方原因等导致本服务无法正常提供或用户遭受损失的,我们不承担责任。​
+			六、协议变更与终止​
+			变更:我们有权根据需要对本协议进行变更,并在小程序中公布。用户继续使用本服务即视为同意变更后的协议。​
+			终止:用户可随时停止使用本服务。我们有权在用户违反本协议或法律法规时,终止向用户提供服务。​
+			七、争议解决​
+			本协议的签订、履行、解释及争议解决均适用中华人民共和国法律。如双方发生争议,应首先友好协商解决;协商不成的,任何一方均有权向有管辖权的人民法院提起诉讼。​
+			宏匠唐卡隐私协议​
+			我们深知用户隐私保护的重要性,特制定本《隐私协议》(以下简称 “本协议”),向您说明我们如何收集、使用、存储和保护您的个人信息。​
+			一、信息收集​
+			注册信息:当您注册账号时,我们会收集您的姓名、手机号码、电子邮箱等信息,用于账号注册及身份验证。​
+			使用信息:在您使用本服务过程中,我们会收集您的操作记录、使用时长、项目信息等,以优化服务及提升用户体验。​
+			设备信息:我们会收集您的设备型号、操作系统、IP 地址等信息,以保障服务的正常运行及安全防护。​
+			二、信息使用​
+			提供服务:我们使用收集的信息为您提供本服务,包括但不限于项目管理功能的实现、问题反馈处理等。​
+			个性化服务:我们可能会根据您的使用习惯及偏好,为您提供个性化的服务推荐。​
+			安全保障:我们会使用信息进行安全监测、风险防范,保障服务及用户信息安全。​
+			三、信息存储​
+			存储地点:我们将您的信息存储在中国境内。如需跨境传输,我们会按照法律法规的要求进行安全评估并取得您的同意。​
+			存储期限:我们会在为您提供服务所必需的期限内存储您的信息,法律法规另有规定的除外。​
+			四、信息保护​
+			我们采取技术和管理措施,保障您的信息安全,防止信息泄露、丢失或被篡改。​
+			我们会对接触您信息的员工进行严格的保密培训,并限制访问权限。​
+			五、信息共享与披露​
+			共享:我们不会向第三方共享您的个人信息,除非获得您的明确同意,或为履行法律法规规定的义务、保护我们及其他用户的合法权益所必需。​
+			披露:在法律要求或司法程序需要时,我们可能会披露您的信息。​
+			六、用户权利​
+			访问权:您有权访问您的个人信息。​
+			更正权:如您发现信息有误,有权要求我们更正。​
+			删除权:在符合法律法规规定的情况下,您有权要求我们删除您的信息。​
+			撤回同意权:您有权随时撤回对我们收集和使用您信息的同意,但可能影响服务的正常提供。​
+			七、协议变更​
+			我们有权根据法律法规及业务发展需要对本协议进行变更,并在小程序中公布。变更后的协议自公布之日起生效。​
+			如您对本协议有任何疑问或建议,请联系我们的客服人员。联系方式:18602013598。
+		</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		components: {},
+		data() {
+			return {};
+		},
+		computed: {},
+		onLoad() {
+
+		},
+		methods: {
+			// getRichText() {
+			// 	this.$http('common.richText', {
+			// 		id: this.$Route.query.id
+			// 	}).then(res => {
+			// 		if (res.code === 1) {
+			// 			this.richText = res.data;
+			// 			uni.setNavigationBarTitle({
+			// 				title: res.data.title
+			// 			});
+			// 		}
+			// 	});
+			// }
+		}
+	};
+</script>
+
+<style lang="scss">
+	.content_box {
+		background: #fff;
+		padding: 30rpx;
+	}
+</style>

+ 1044 - 0
packageUser/pages/share/index.vue

@@ -0,0 +1,1044 @@
+<template>
+	<view class="page">
+		<!-- 顶部背景装饰 -->
+		<view class="bg-decoration">
+			<view class="bg-circle bg-circle-1"></view>
+			<view class="bg-circle bg-circle-2"></view>
+			<view class="bg-circle bg-circle-3"></view>
+		</view>
+
+		<!-- 顶部标题 -->
+		<view class="header">
+			<view class="header-icon">
+				<image class="icon-gift" src="/static/images/gift.png" mode="aspectFit"></image>
+			</view>
+			<text class="title">邀请好友</text>
+			<text class="subtitle">邀请好友加入,共享收益好礼</text>
+			<view class="title-decoration"></view>
+		</view>
+
+		<!-- 邀请方式切换 -->
+		<view class="tabs-container">
+			<view class="tabs">
+				<view class="tab" :class="{'active': activeMethod === 'wechat'}" @click="selectMethod('wechat')">
+					<uni-icons class="tab-icon" type="weixin" size="20"></uni-icons>
+					<text class="tab-text">微信分享</text>
+				</view>
+				<view class="tab" :class="{'active': activeMethod === 'account'}" @click="selectMethod('account')">
+					<uni-icons class="tab-icon" type="person" size="20"></uni-icons>
+					<text class="tab-text">用户名邀请</text>
+				</view>
+				<view class="tab-slider" :class="{'slide-right': activeMethod === 'account'}"></view>
+			</view>
+		</view>
+
+		<!-- 账号邀请 -->
+		<view class="content animate-fade-in" v-if="activeMethod === 'account'">
+			<view class="search-card">
+				<view class="search-header">
+					<view class="search-title">
+						<uni-icons type="search" size="18"></uni-icons>
+						<text class="search-title-text">搜索好友</text>
+					</view>
+				</view>
+				<view class="search-box">
+					<view class="search-input-wrapper">
+						<text class="search-prefix-icon">@</text>
+						<input class="search-input" v-model="accountInput" placeholder="请输入好友用户名"
+							@input="onAccountInput" />
+					</view>
+					<button class="search-btn" @click="searchUser" :disabled="!accountInput.trim()">
+						<text class="btn-text">搜索</text>
+					</button>
+				</view>
+			</view>
+
+			<view class="user-result-card animate-slide-up" v-if="searchedUser">
+				<view class="user-result">
+					<view class="avatar-wrapper">
+						<image class="avatar" :src="searchedUser.avatar || '/static/images/avatar.png'"
+							mode="aspectFill"></image>
+						<view class="avatar-border"></view>
+					</view>
+					<view class="info">
+						<text class="name">{{ searchedUser.account }}</text>
+					</view>
+					<button class="invite-btn" @click="sendInviteByAccount">
+						<uni-icons class="invite-icon" type="email" size="14" color="#fff"></uni-icons>
+						<text class="invite-text">邀请</text>
+					</button>
+				</view>
+			</view>
+		</view>
+
+		<!-- 微信分享 -->
+		<view class="content animate-fade-in" v-if="activeMethod === 'wechat'">
+			<view class="share-card">
+				<view class="share-bg">
+					<view class="share-bg-circle"></view>
+				</view>
+				<view class="share-content">
+					<view class="share-icon-wrapper">
+						<view class="share-icon">🚀</view>
+						<view class="share-icon-bg"></view>
+					</view>
+					<text class="share-title">分享给好友</text>
+					<text class="share-desc">通过微信分享邀请链接,让更多朋友加入</text>
+					<button class="share-btn" open-type="share">
+						<uni-icons class="share-btn-icon" type="redo" size="18" color="#fff"></uni-icons>
+						<text class="share-btn-text">立即分享</text>
+					</button>
+				</view>
+			</view>
+		</view>
+
+		<!-- 邀请记录 -->
+		<view class="records-card">
+			<view class="records-header">
+				<view class="records-title-wrapper">
+					<uni-icons class="records-icon" type="list" size="18"></uni-icons>
+					<text class="records-title">邀请记录</text>
+				</view>
+				<view class="records-count-wrapper">
+					<text class="records-count">{{ inviteRecords.length }}</text>
+					<text class="records-unit">人</text>
+				</view>
+			</view>
+
+			<view class="records-list" v-if="inviteRecords.length > 0">
+				<view class="record-item animate-slide-in" v-for="(record, index) in inviteRecords" :key="index"
+					:style="{ 'animation-delay': (index * 0.1) + 's' }">
+					<view class="record-avatar-wrapper">
+						<image class="record-avatar" :src="record.avatar || '/static/images/avatar.png'"
+							mode="aspectFill"></image>
+						<view class="record-avatar-border"></view>
+					</view>
+					<view class="record-info">
+						<text class="record-name">{{ record.account }}</text>
+						<view class="record-meta">
+							<text class="record-time">{{ formatTime(record.createTime) }}</text>
+							<text class="record-type"
+								v-if="record.invitationType">{{ getInvitationTypeText(record.invitationType) }}</text>
+						</view>
+					</view>
+					<view class="record-status-wrapper">
+						<view class="status-dot" :class="getStatusClass(record.status)"></view>
+						<text class="record-status">{{ record.status }}</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="empty1" v-else>
+				<view class="flex-items-plus">
+					<image src="@/static/images/empty.png" class="empty "></image>
+				</view>
+				<view class="empty-text">暂无邀请记录</view>
+				<view class="empty-desc">快去邀请好友加入吧</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		findByAccount,
+		sendInvitation,
+		getMyInvitations
+	} from '@/config/api.js'
+	import {
+		shareImg
+	} from '@/common/config.js'
+	export default {
+		data() {
+			return {
+				activeMethod: 'wechat', // 当前选择的邀请方式,默认为微信分享
+				accountInput: '', // 账号输入
+				searchedUser: null, // 搜索到的用户
+				inviteRecords: [], // 邀请记录
+				userInfo: {} // 当前用户信息
+			}
+		},
+
+		onLoad() {
+			this.getUserInfo()
+			this.getInviteRecords()
+		},
+
+		// 微信小程序分享配置
+		onShareAppMessage() {
+			const userInfo = uni.getStorageSync('user') || {}
+			console.log(userInfo, "分享用户信息")
+			return {
+				title: `${userInfo.account || '好友'}邀请您加入`,
+				path: `/pages/index/indexNew?inviter=${userInfo.id ||userInfo.user_id|| ''}`,
+				imageUrl: shareImg // 分享图片,需要添加
+			}
+		},
+
+		methods: {
+			// 获取用户信息
+			getUserInfo() {
+				this.userInfo = uni.getStorageSync('user') || {}
+			},
+
+			// 选择邀请方式
+			selectMethod(method) {
+				this.activeMethod = method
+				if (method === 'account') {
+					this.searchedUser = null
+					this.accountInput = ''
+				}
+			},
+
+			// 账号输入处理
+			onAccountInput(e) {
+				this.accountInput = e.detail.value
+				if (!this.accountInput.trim()) {
+					this.searchedUser = null
+				}
+			},
+
+			// 搜索用户
+			async searchUser() {
+				if (!this.accountInput.trim()) {
+					uni.showToast({
+						title: '请输入用户名',
+						icon: 'none'
+					})
+					return
+				}
+
+				try {
+					uni.showLoading({
+						title: '搜索中...'
+					})
+
+					const res = await findByAccount(this.accountInput.trim())
+
+					if (res.code === 200) {
+						this.searchedUser = res.data
+					} else {
+						this.searchedUser = null
+						uni.showToast({
+							title: res.msg || '用户不存在',
+							icon: 'none'
+						})
+					}
+				} catch (error) {
+					console.error('搜索用户失败:', error)
+					uni.showToast({
+						title: error.msg,
+						icon: 'none'
+					})
+				} finally {
+					uni.hideLoading()
+				}
+			},
+
+			// 通过账号发送邀请
+			async sendInviteByAccount() {
+				if (!this.searchedUser) {
+					return
+				}
+
+				try {
+					uni.showLoading({
+						title: '发送邀请中...'
+					})
+
+					const res = await sendInvitation(this.searchedUser.id)
+
+					if (res.code === 200) {
+						uni.showToast({
+							title: '邀请发送成功',
+							icon: 'success'
+						})
+
+						// 重新获取邀请记录
+						this.getInviteRecords()
+
+						// 清空搜索结果
+						this.searchedUser = null
+						this.accountInput = ''
+					} else {
+						uni.showToast({
+							title: res.msg || '邀请发送失败',
+							icon: 'none'
+						})
+					}
+				} catch (error) {
+					console.error('发送邀请失败:', error)
+					uni.showToast({
+						title: error.msg,
+						icon: 'none'
+					})
+				} finally {
+					uni.hideLoading()
+				}
+			},
+
+			// 获取邀请记录
+			async getInviteRecords() {
+				try {
+					const res = await getMyInvitations()
+
+					if (res.code === 200) {
+						this.inviteRecords = res.data || []
+					}
+				} catch (error) {
+					console.error('获取邀请记录失败:', error)
+				}
+			},
+
+			// 格式化时间
+			formatTime(time) {
+				if (!time) return ''
+				const date = new Date(time)
+				const now = new Date()
+				const diff = now.getTime() - date.getTime()
+				const days = Math.floor(diff / (1000 * 60 * 60 * 24))
+
+				if (days === 0) {
+					return '今天'
+				} else if (days === 1) {
+					return '昨天'
+				} else if (days < 7) {
+					return `${days}天前`
+				} else {
+					return date.toLocaleDateString()
+				}
+			},
+
+			// 获取邀请类型文字
+			getInvitationTypeText(type) {
+				switch (type) {
+					case 1:
+						return '用户名邀请'
+					case 2:
+						return '微信分享'
+					default:
+						return '未知类型'
+				}
+			},
+
+			// 获取状态样式类
+			getStatusClass(status) {
+				switch (status) {
+					case '已接受':
+						return 'status-accepted'
+					case '已拒绝':
+						return 'status-rejected'
+					case '待处理':
+					default:
+						return 'status-pending'
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	/* 页面主体 */
+	.page {
+		min-height: 100vh;
+		background: linear-gradient(135deg, #e8f2ff 0%, #f0f4ff 100%);
+		position: relative;
+		overflow: hidden;
+	}
+
+	/* 背景装饰 */
+	.bg-decoration {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		pointer-events: none;
+		z-index: 0;
+	}
+
+	.bg-circle {
+		position: absolute;
+		border-radius: 50%;
+		background: rgba(255, 255, 255, 0.6);
+		animation: float 6s ease-in-out infinite;
+	}
+
+	.bg-circle-1 {
+		width: 200rpx;
+		height: 200rpx;
+		top: 10%;
+		right: -100rpx;
+		animation-delay: 0s;
+	}
+
+	.bg-circle-2 {
+		width: 150rpx;
+		height: 150rpx;
+		top: 30%;
+		left: -75rpx;
+		animation-delay: 2s;
+	}
+
+	.bg-circle-3 {
+		width: 100rpx;
+		height: 100rpx;
+		top: 60%;
+		right: 20rpx;
+		animation-delay: 4s;
+	}
+
+	@keyframes float {
+
+		0%,
+		100% {
+			transform: translateY(0px);
+		}
+
+		50% {
+			transform: translateY(-20rpx);
+		}
+	}
+
+	/* 头部区域 */
+	.header {
+		text-align: center;
+		padding: 80rpx 24rpx 60rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.header-icon {
+		margin-bottom: 20rpx;
+	}
+
+	.icon-gift {
+		width: 80rpx;
+		height: 80rpx;
+		display: block;
+		margin: 0 auto;
+		animation: bounce 2s infinite;
+	}
+
+	@keyframes bounce {
+
+		0%,
+		20%,
+		50%,
+		80%,
+		100% {
+			transform: translateY(0);
+		}
+
+		40% {
+			transform: translateY(-10rpx);
+		}
+
+		60% {
+			transform: translateY(-5rpx);
+		}
+	}
+
+	.title {
+		display: block;
+		font-size: 48rpx;
+		font-weight: 700;
+		color: #4a5568;
+		margin-bottom: 20rpx;
+		text-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+	}
+
+	.subtitle {
+		font-size: 28rpx;
+		color: #718096;
+		margin-bottom: 30rpx;
+	}
+
+	.title-decoration {
+		width: 100rpx;
+		height: 4rpx;
+		background: linear-gradient(90deg, #a0aec0, transparent);
+		margin: 0 auto;
+		border-radius: 2rpx;
+	}
+
+	/* 标签容器 */
+	.tabs-container {
+		padding: 0 24rpx;
+		margin-bottom: 40rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.tabs {
+		display: flex;
+		background: rgba(255, 255, 255, 0.95);
+		border-radius: 50rpx;
+		padding: 8rpx;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+		backdrop-filter: blur(10rpx);
+		position: relative;
+	}
+
+	.tab {
+		flex: 1;
+		text-align: center;
+		padding: 20rpx 0;
+		border-radius: 42rpx;
+		transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+		position: relative;
+		z-index: 2;
+		cursor: pointer;
+	}
+
+	.tab-icon {
+		display: block;
+		margin-bottom: 6rpx;
+	}
+
+	.tab-text {
+		font-size: 26rpx;
+		color: #666;
+		font-weight: 500;
+	}
+
+	.tab.active .tab-text {
+		color: #fff;
+		font-weight: 600;
+	}
+
+	.tab.active .tab-icon {
+		color: #fff;
+	}
+
+	.tab-slider {
+		position: absolute;
+		top: 8rpx;
+		left: 8rpx;
+		width: calc(50% - 8rpx);
+		height: calc(100% - 16rpx);
+		background: linear-gradient(135deg, #90cdf4, #a2b5f0);
+		border-radius: 42rpx;
+		transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+		z-index: 1;
+		box-shadow: 0 4rpx 16rpx rgba(144, 205, 244, 0.3);
+	}
+
+	.tab-slider.slide-right {
+		transform: translateX(100%);
+	}
+
+	/* 内容区域 */
+	.content {
+		padding: 0 24rpx;
+		margin-bottom: 40rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	/* 动画效果 */
+	.animate-fade-in {
+		animation: fadeIn 0.5s ease-out;
+	}
+
+	.animate-slide-up {
+		animation: slideUp 0.5s ease-out;
+	}
+
+	.animate-slide-in {
+		animation: slideIn 0.5s ease-out both;
+	}
+
+	@keyframes fadeIn {
+		from {
+			opacity: 0;
+			transform: translateY(20rpx);
+		}
+
+		to {
+			opacity: 1;
+			transform: translateY(0);
+		}
+	}
+
+	@keyframes slideUp {
+		from {
+			opacity: 0;
+			transform: translateY(40rpx);
+		}
+
+		to {
+			opacity: 1;
+			transform: translateY(0);
+		}
+	}
+
+	@keyframes slideIn {
+		from {
+			opacity: 0;
+			transform: translateX(-30rpx);
+		}
+
+		to {
+			opacity: 1;
+			transform: translateX(0);
+		}
+	}
+
+	/* 搜索卡片 */
+	.search-card {
+		background: rgba(255, 255, 255, 0.95);
+		border-radius: 24rpx;
+		padding: 32rpx;
+		margin-bottom: 24rpx;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.search-header {
+		margin-bottom: 24rpx;
+	}
+
+	.search-title {
+		display: flex;
+		align-items: center;
+		gap: 8rpx;
+	}
+
+	.search-title-text {
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #333;
+	}
+
+	.search-box {
+		display: flex;
+		gap: 16rpx;
+		align-items: center;
+	}
+
+	.search-input-wrapper {
+		flex: 1;
+		display: flex;
+		align-items: center;
+		background: #f8f9fa;
+		border-radius: 16rpx;
+		padding: 0 20rpx;
+		height: 80rpx;
+		border: 2rpx solid transparent;
+		transition: all 0.3s ease;
+	}
+
+	.search-input-wrapper:focus-within {
+		border-color: #90cdf4;
+		box-shadow: 0 0 0 4rpx rgba(144, 205, 244, 0.1);
+	}
+
+	.search-prefix-icon {
+		font-size: 32rpx;
+		color: #999;
+		margin-right: 12rpx;
+		font-weight: 600;
+	}
+
+	.search-input {
+		flex: 1;
+		height: 100%;
+		border: none;
+		background: transparent;
+		font-size: 28rpx;
+		color: #333;
+	}
+
+	.search-btn {
+		height: 80rpx;
+		padding: 0 32rpx;
+		background: linear-gradient(135deg, #90cdf4, #a2b5f0);
+		color: #fff;
+		border: none;
+		border-radius: 16rpx;
+		font-size: 28rpx;
+		font-weight: 600;
+		box-shadow: 0 4rpx 16rpx rgba(144, 205, 244, 0.2);
+		transition: all 0.3s ease;
+	}
+
+	.search-btn:active {
+		transform: translateY(2rpx);
+		box-shadow: 0 2rpx 8rpx rgba(144, 205, 244, 0.2);
+	}
+
+	.search-btn[disabled] {
+		background: #ccc;
+		box-shadow: none;
+	}
+
+	/* 用户结果卡片 */
+	.user-result-card {
+		background: rgba(255, 255, 255, 0.95);
+		border-radius: 24rpx;
+		padding: 32rpx;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.user-result {
+		display: flex;
+		align-items: center;
+	}
+
+	.avatar-wrapper {
+		position: relative;
+		margin-right: 24rpx;
+	}
+
+	.avatar {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+	}
+
+	.avatar-border {
+		position: absolute;
+		top: -4rpx;
+		left: -4rpx;
+		width: 88rpx;
+		height: 88rpx;
+		border-radius: 50%;
+		border: 2rpx solid #90cdf4;
+		opacity: 0.4;
+	}
+
+	.info {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		gap: 8rpx;
+	}
+
+	.name {
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #333;
+	}
+
+	.account {
+		font-size: 26rpx;
+		color: #999;
+	}
+
+	.invite-btn {
+		display: flex;
+		align-items: center;
+		gap: 8rpx;
+		height: 64rpx;
+		padding: 0 24rpx;
+		background: linear-gradient(135deg, #81c995, #a2d2a4);
+		color: #fff;
+		border: none;
+		border-radius: 32rpx;
+		font-size: 26rpx;
+		font-weight: 600;
+		box-shadow: 0 4rpx 16rpx rgba(129, 201, 149, 0.2);
+		transition: all 0.3s ease;
+	}
+
+	.invite-btn:active {
+		transform: translateY(2rpx);
+	}
+
+	.invite-icon {
+		margin-right: 4rpx;
+	}
+
+	/* 分享卡片 */
+	.share-card {
+		background: rgba(255, 255, 255, 0.95);
+		border-radius: 24rpx;
+		overflow: hidden;
+		position: relative;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+		backdrop-filter: blur(10rpx);
+	}
+
+	.share-bg {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		overflow: hidden;
+	}
+
+	.share-bg-circle {
+		position: absolute;
+		width: 200rpx;
+		height: 200rpx;
+		background: linear-gradient(135deg, rgba(144, 205, 244, 0.1), rgba(162, 181, 240, 0.1));
+		border-radius: 50%;
+		top: -50rpx;
+		right: -50rpx;
+	}
+
+	.share-content {
+		padding: 60rpx 32rpx;
+		text-align: center;
+		position: relative;
+		z-index: 1;
+	}
+
+	.share-icon-wrapper {
+		position: relative;
+		display: inline-block;
+		margin-bottom: 32rpx;
+	}
+
+	.share-icon {
+		font-size: 80rpx;
+		position: relative;
+		z-index: 2;
+	}
+
+	.share-icon-bg {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		width: 120rpx;
+		height: 120rpx;
+		background: linear-gradient(135deg, rgba(144, 205, 244, 0.15), rgba(162, 181, 240, 0.15));
+		border-radius: 50%;
+		z-index: 1;
+	}
+
+	.share-title {
+		display: block;
+		font-size: 36rpx;
+		font-weight: 600;
+		color: #333;
+		margin-bottom: 16rpx;
+	}
+
+	.share-desc {
+		display: block;
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 48rpx;
+		line-height: 1.5;
+	}
+
+	.share-btn {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		gap: 12rpx;
+		width: 320rpx;
+		height: 80rpx;
+		background: linear-gradient(135deg, #90cdf4, #a2b5f0);
+		color: #fff;
+		border: none;
+		border-radius: 40rpx;
+		font-size: 30rpx;
+		font-weight: 600;
+		margin: 0 auto;
+		box-shadow: 0 6rpx 24rpx rgba(144, 205, 244, 0.25);
+		transition: all 0.3s ease;
+	}
+
+	.share-btn:active {
+		transform: translateY(2rpx);
+	}
+
+	.share-btn-icon {
+		margin-right: 8rpx;
+	}
+
+	/* 邀请记录卡片 */
+	.records-card {
+		background: rgba(255, 255, 255, 0.95);
+		border-radius: 24rpx;
+		padding: 32rpx;
+		margin: 0 24rpx 40rpx;
+		box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+		backdrop-filter: blur(10rpx);
+		position: relative;
+		z-index: 1;
+	}
+
+	.records-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding-bottom: 24rpx;
+		margin-bottom: 24rpx;
+		border-bottom: 2rpx solid #f0f0f0;
+	}
+
+	.records-title-wrapper {
+		display: flex;
+		align-items: center;
+		gap: 12rpx;
+	}
+
+	.records-icon {
+		margin-right: 8rpx;
+	}
+
+	.records-title {
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #333;
+	}
+
+	.records-count-wrapper {
+		display: flex;
+		align-items: center;
+		gap: 4rpx;
+	}
+
+	.records-count {
+		font-size: 32rpx;
+		font-weight: 700;
+		color: #90cdf4;
+	}
+
+	.records-unit {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.records-list {
+		display: flex;
+		flex-direction: column;
+		gap: 20rpx;
+	}
+
+	.record-item {
+		display: flex;
+		align-items: center;
+		padding: 24rpx;
+		background: #f8f9fa;
+		border-radius: 16rpx;
+		transition: all 0.3s ease;
+	}
+
+	.record-item:hover {
+		background: #f0f1f5;
+		transform: translateX(8rpx);
+	}
+
+	.record-avatar-wrapper {
+		position: relative;
+		margin-right: 20rpx;
+	}
+
+	.record-avatar {
+		width: 60rpx;
+		height: 60rpx;
+		border-radius: 50%;
+	}
+
+	.record-avatar-border {
+		position: absolute;
+		top: -2rpx;
+		left: -2rpx;
+		width: 64rpx;
+		height: 64rpx;
+		border-radius: 50%;
+		border: 2rpx solid #90cdf4;
+		opacity: 0.3;
+	}
+
+	.record-info {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		gap: 6rpx;
+	}
+
+	.record-name {
+		font-size: 28rpx;
+		color: #333;
+		font-weight: 600;
+	}
+
+	.record-meta {
+		display: flex;
+		flex-direction: column;
+		gap: 4rpx;
+	}
+
+	.record-time {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.record-type {
+		font-size: 22rpx;
+		color: #666;
+		background: rgba(144, 205, 244, 0.1);
+		padding: 2rpx 8rpx;
+		border-radius: 8rpx;
+		align-self: flex-start;
+	}
+
+	.record-status-wrapper {
+		display: flex;
+		align-items: center;
+		gap: 8rpx;
+	}
+
+	.status-dot {
+		width: 12rpx;
+		height: 12rpx;
+		border-radius: 50%;
+		transition: all 0.3s ease;
+	}
+
+	.status-dot.status-pending {
+		background: #ffb366;
+	}
+
+	.status-dot.status-accepted {
+		background: #81c995;
+	}
+
+	.status-dot.status-rejected {
+		background: #ff6b6b;
+	}
+
+	.record-status {
+		font-size: 24rpx;
+		color: #81c995;
+		font-weight: 600;
+	}
+
+	/* 空状态 */
+	.empty1 {
+		text-align: center;
+		padding: 80rpx 0;
+	}
+
+	.flex-items-plus {
+		display: flex;
+		justify-content: center;
+		margin-bottom: 24rpx;
+		opacity: 0.5;
+	}
+
+	button::after {
+		border: none;
+	}
+
+	.empty-text {
+		font-size: 28rpx;
+		color: #999;
+		margin-bottom: 12rpx;
+		font-weight: 500;
+	}
+
+	.empty-desc {
+		font-size: 24rpx;
+		color: #ccc;
+	}
+</style>

+ 1118 - 0
packageUser/pages/team/index.vue

@@ -0,0 +1,1118 @@
+<template>
+	<view class="team-container">
+		<!-- 团队数据概览 -->
+		<view class="team-overview">
+			<view class="overview-card">
+				<view class="card-header">
+					<text class="card-title">团队总览</text>
+				</view>
+				<view class="stats-grid">
+					<view class="stat-item primary">
+						<view class="stat-icon">
+							<u-icon name="account" size="36" color="#8B5CF6"></u-icon>
+						</view>
+						<view class="stat-content">
+							<text class="stat-value">{{teamData.level1Count}}</text>
+							<text class="stat-label">直属下级</text>
+						</view>
+					</view>
+					<view class="stat-item secondary">
+						<view class="stat-icon">
+							<u-icon name="account-fill" size="36" color="#6366F1"></u-icon>
+						</view>
+						<view class="stat-content">
+							<text class="stat-value">{{teamData.level2Count}}</text>
+							<text class="stat-label">间接下级</text>
+						</view>
+					</view>
+					<view class="stat-item success">
+						<view class="stat-icon">
+							<u-icon name="shopping-cart" size="36" color="#10B981"></u-icon>
+						</view>
+						<view class="stat-content">
+							<text class="stat-value">{{teamData.totalBuyCount}}</text>
+							<text class="stat-label">总购买数</text>
+						</view>
+					</view>
+					<view class="stat-item warning">
+						<view class="stat-icon">
+							<u-icon name="rmb-circle" size="36" color="#F59E0B"></u-icon>
+						</view>
+						<view class="stat-content">
+							<text class="stat-value">¥{{teamData.totalAmount}}</text>
+							<text class="stat-label">总金额</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 团队成员列表 -->
+		<view class="team-list">
+			<view class="list-header">
+				<view class="header-left">
+					<text class="header-title">我的团队</text>
+				</view>
+				<view class="header-right">
+					<view class="count-display">
+						<text class="total-count">共{{teamData.totalCount}}人</text>
+						<view class="level-indicators">
+							<view class="level-indicator" v-if="teamData.level1Count > 0">
+								<view class="indicator-dot level-1"></view>
+								<text>{{teamData.level1Count}}人(1级)</text>
+							</view>
+							<view class="level-indicator" v-if="teamData.level2Count > 0">
+								<view class="indicator-dot level-2"></view>
+								<text>{{teamData.level2Count}}人(2级)</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 树形结构展示 -->
+			<view class="team-tree">
+				<!-- 根节点(我) -->
+				<view class="tree-node root-node" :data-level="0">
+					<view class="node-content" @click="toggleNode('root')">
+						<view class="node-info">
+							<view class="avatar-wrapper">
+								<image class="avatar" :src="'/static/images/avatar.png'" mode="aspectFill"></image>
+								<view class="avatar-tag root-tag">我</view>
+							</view>
+							<view class="info">
+								<text class="name">我的账号</text>
+								<text class="level">{{getLevelText(user.level || 0)}}</text>
+								<!-- 个人购买统计 -->
+								<view class="personal-stats"
+									v-if="personalStats.productCount > 0 || personalStats.totalAmount > 0">
+									<text class="stat">购买{{personalStats.productCount}}件</text>
+									<text class="stat">金额¥{{personalStats.totalAmount}}</text>
+								</view>
+								<view class="team-summary" v-if="teamData.totalCount > 0">
+									<text class="summary-text">团队{{teamData.totalCount}}人</text>
+								</view>
+							</view>
+						</view>
+						<view class="node-actions">
+							<view class="expand-info" v-if="teamData.level1Count > 0">
+								<text class="member-preview">{{teamData.level1Count}}个直属</text>
+							</view>
+							<view class="btn-order1" @click.stop="viewOrders(user.id)">
+								<text>我的订单</text>
+							</view>
+							<view class="expand-icon" :class="{ 'expanded': expandedNodes.root }">
+								<u-icon :name="expandedNodes.root ? 'arrow-up' : 'arrow-down'" size="28"
+									:color="expandedNodes.root ? '#8B5CF6' : '#64748b'"></u-icon>
+							</view>
+						</view>
+					</view>
+
+					<!-- 子节点 -->
+					<view class="children level-1-children" v-if="expandedNodes.root && teamData.members.length > 0">
+						<view class="tree-node level-1-node" v-for="(member, index) in teamData.members"
+							:key="member.id" :data-level="1">
+							<view class="node-content" @click="toggleNode(member.id)">
+								<view class="node-info">
+									<view class="avatar-wrapper">
+										<image class="avatar" :src="member.avatar || '/static/images/avatar.png'"
+											mode="aspectFill"></image>
+										<view class="level-badge level-1">L1</view>
+									</view>
+									<view class="info">
+										<text class="name">{{member.account}}</text>
+										<text class="level">{{getLevelText(member.level)}}</text>
+										<view class="member-stats">
+											<text class="stat">购买{{member.buyCount || 0}}件</text>
+											<text class="stat">金额¥{{formatAmount(member.amount)}}</text>
+										</view>
+										<view class="team-summary" v-if="member.hasChildren">
+											<text class="summary-text">有下级团队</text>
+										</view>
+									</view>
+								</view>
+								<view class="node-actions">
+									<view class="action-buttons">
+										<view class="btn-order" @click.stop="viewOrders(member.id)">
+											<u-icon name="list" size="22" color="#8B5CF6"></u-icon>
+											<text>订单</text>
+										</view>
+										<!-- <view class="member-preview" v-if="member.hasChildren">
+											<view class="preview-indicator">
+												<u-icon name="arrow-right" size="16" color="#6366F1"></u-icon>
+											</view>
+											<text class="preview-label">有下级</text>
+										</view> -->
+									</view>
+									<view class="expand-icon" v-if="member.hasChildren"
+										:class="{ 'expanded': expandedNodes[member.id] }">
+										<u-icon :name="expandedNodes[member.id] ? 'arrow-up' : 'arrow-down'" size="26"
+											:color="expandedNodes[member.id] ? '#8B5CF6' : '#64748b'"></u-icon>
+									</view>
+								</view>
+							</view>
+
+							<!-- 孙节点 -->
+							<view class="children level-2-children"
+								v-if="expandedNodes[member.id] && member.children && member.children.length > 0">
+								<view class="tree-node level-2-node" v-for="(child, childIndex) in member.children"
+									:key="child.id" :data-level="2">
+									<view class="node-content">
+										<view class="node-info">
+											<view class="avatar-wrapper">
+												<image class="avatar" :src="child.avatar || '/static/images/avatar.png'"
+													mode="aspectFill"></image>
+												<view class="level-badge level-2">L2</view>
+											</view>
+											<view class="info">
+												<text class="name">{{child.account}}</text>
+												<text class="level">{{getLevelText(child.level)}}</text>
+												<view class="member-stats">
+													<text class="stat">购买{{child.buyCount || 0}}件</text>
+													<text class="stat">金额¥{{formatAmount(child.amount)}}</text>
+												</view>
+											</view>
+										</view>
+										<view class="node-actions">
+											<view class="action-buttons">
+												<view class="btn-order" @click.stop="viewOrders(child.id)">
+													<u-icon name="list" size="20" color="#8B5CF6"></u-icon>
+													<text>订单</text>
+												</view>
+											</view>
+										</view>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+
+				<view v-if="teamData.members.length === 0" class="empty-state">
+					<view class="empty-content">
+						<view class="flex-items-plus">
+							<image src="@/static/images/empty.png" class="empty"></image>
+						</view>
+						<text class="empty-title">暂无团队成员</text>
+						<text class="empty-desc">您还没有团队成员,快去邀请朋友加入吧!</text>
+						<view class="empty-actions">
+							<view class="action-button primary" @click="inviteFriends">
+								<u-icon name="plus" size="24" color="#fff"></u-icon>
+								<text>邀请好友</text>
+							</view>
+							<view class="action-button secondary" @click="refreshData">
+								<u-icon name="reload" size="24" color="#8B5CF6"></u-icon>
+								<text>刷新数据</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getMemberlazyList,
+		memberDetail,
+		teamStatistics,
+		countPaidProducts
+	} from '@/config/api';
+
+	export default {
+		data() {
+			return {
+				expandedNodes: {
+					root: true // 默认展开根节点
+				},
+				user: {},
+				personalStats: {
+					productCount: 0,
+					totalAmount: '0.00'
+				},
+				teamData: {
+					level1Count: 0,
+					level2Count: 0,
+					totalBuyCount: 0,
+					totalAmount: '0.00',
+					totalCount: 0,
+					members: []
+				}
+			}
+		},
+		onLoad() {
+			this.getMember()
+			this.getTeamStatistics()
+			this.getPersonalStats()
+		},
+		methods: {
+			getMember() {
+				memberDetail().then((res) => {
+					this.user = res.data
+					this.getTeamData()
+				})
+			},
+
+			async getPersonalStats() {
+				try {
+					const response = await countPaidProducts()
+					if (response.success && response.data) {
+						const data = response.data
+						this.personalStats.productCount = parseInt(data.productCount) || 0
+						this.personalStats.totalAmount = parseFloat(data.totalAmount || 0).toFixed(2)
+					}
+				} catch (error) {
+					console.error('获取个人购买统计数据失败:', error)
+				}
+			},
+
+			async getTeamStatistics() {
+				try {
+					const response = await teamStatistics()
+					if (response.success && response.data) {
+						const data = response.data
+						this.teamData.totalCount = parseInt(data.totalTeamMembers) || 0
+						this.teamData.level1Count = parseInt(data.directSubordinates) || 0
+						this.teamData.level2Count = parseInt(data.secondLevelSubordinates) || 0
+						this.teamData.totalBuyCount = parseInt(data.totalPurchaseCount) || 0
+
+						const amount = data.totalOrderAmount
+						if (amount !== null && amount !== undefined && amount !== '') {
+							this.teamData.totalAmount = parseFloat(amount).toFixed(2)
+						} else {
+							this.teamData.totalAmount = '0.00'
+						}
+					}
+				} catch (error) {
+					console.error('获取团队统计数据失败:', error)
+				}
+			},
+
+			async getTeamData() {
+				try {
+					const response = await getMemberlazyList({
+						id: this.user.id
+					})
+
+					if (response.success && response.data) {
+						// 后端已经返回树形结构,直接使用
+						this.teamData.members = response.data;
+
+						// 更新统计数据
+						this.updateTeamStats();
+
+						// 默认展开所有有子节点的成员
+						this.expandAllNodes();
+					}
+				} catch (error) {
+					console.error('获取团队数据失败:', error)
+					uni.showToast({
+						title: '获取团队数据失败',
+						icon: 'none'
+					})
+				}
+			},
+
+			buildTreeStructure(flatData) {
+				if (!flatData || flatData.length === 0) return [];
+
+				console.log('开始构建树形结构,原始数据:', flatData);
+				console.log('当前用户ID:', this.user.id);
+
+				// 数据预处理
+				const processedData = flatData.map(member => ({
+					...member,
+					buyCount: member.buyCount || 0,
+					amount: member.amount || 0,
+					hasChildren: member.hasChildren || false,
+					children: []
+				}));
+
+				console.log('预处理后的数据:', processedData);
+
+				// 创建ID映射
+				const memberMap = {};
+				processedData.forEach(member => {
+					memberMap[member.id] = member;
+				});
+
+				console.log('成员ID映射:', memberMap);
+
+				// 构建树形结构
+				const treeData = [];
+				const level1Members = [];
+				const level2Members = [];
+
+				processedData.forEach(member => {
+					console.log(
+						`处理成员: ${member.account}, ID: ${member.id}, parentId: ${member.parentId}, hasChildren: ${member.hasChildren}`
+					);
+
+					// 判断是否为一级成员(直接下级)
+					if (!member.parentId || member.parentId === this.user.id) {
+						level1Members.push(member);
+						treeData.push(member);
+						console.log(`添加一级成员: ${member.account}`);
+					} else {
+						// 二级成员,添加到父级的children中
+						level2Members.push(member);
+						const parent = memberMap[member.parentId];
+						if (parent) {
+							if (!parent.children) {
+								parent.children = [];
+							}
+							parent.children.push(member);
+							parent.hasChildren = true;
+							console.log(`将二级成员 ${member.account} 添加到父级 ${parent.account} 的children中`);
+						} else {
+							console.log(`警告: 找不到父级成员 ${member.parentId},无法添加二级成员 ${member.account}`);
+						}
+					}
+				});
+
+				console.log('一级成员数量:', level1Members.length);
+				console.log('二级成员数量:', level2Members.length);
+				console.log('构建的树形结构:', treeData);
+
+				return treeData;
+			},
+
+			updateTeamStats() {
+				let level1Count = 0;
+				let level2Count = 0;
+				let totalBuyCount = 0;
+				let totalAmount = 0;
+
+				this.teamData.members.forEach(member => {
+					level1Count++;
+					totalBuyCount += parseInt(member.buyCount) || 0;
+					totalAmount += parseFloat(member.amount) || 0;
+
+					if (member.children && member.children.length > 0) {
+						level2Count += member.children.length;
+						member.children.forEach(child => {
+							totalBuyCount += parseInt(child.buyCount) || 0;
+							totalAmount += parseFloat(child.amount) || 0;
+						});
+					}
+				});
+
+				// 更新统计数据
+				this.teamData.level1Count = level1Count;
+				this.teamData.level2Count = level2Count;
+				this.teamData.totalCount = level1Count + level2Count;
+				this.teamData.totalBuyCount = totalBuyCount;
+				this.teamData.totalAmount = totalAmount.toFixed(2);
+			},
+
+			toggleNode(nodeId) {
+				const isExpanding = !this.expandedNodes[nodeId];
+
+				this.$set(this.expandedNodes, nodeId, isExpanding);
+
+				if (nodeId === 'root') return;
+
+				const member = this.findMemberById(nodeId);
+				if (!member) return;
+
+				if (isExpanding && member.children && member.children.length > 0) {
+					uni.showToast({
+						title: `展开${member.children.length}个下级`,
+						icon: 'success',
+						duration: 1000
+					});
+				}
+			},
+
+			findMemberById(id) {
+				const searchInMembers = (members) => {
+					for (const member of members) {
+						if (member.id === id) return member;
+						if (member.children && member.children.length > 0) {
+							const found = searchInMembers(member.children);
+							if (found) return found;
+						}
+					}
+					return null;
+				};
+				return searchInMembers(this.teamData.members);
+			},
+
+			getLevelText(level) {
+				const levelMap = {
+					0: '普通会员',
+					1: 'S级会员',
+					2: 'SS级会员',
+					3: 'SSS级会员'
+				}
+				return levelMap[level] || '普通会员'
+			},
+
+			formatAmount(amount) {
+				const numAmount = parseFloat(amount) || 0
+				return numAmount.toFixed(2)
+			},
+
+			viewOrders(memberId) {
+				uni.navigateTo({
+					url: `/packageOrder/pages/list/record?memberId=${memberId}`
+				})
+			},
+
+			inviteFriends() {
+				this.$route('/packageUser/pages/share/index')
+			},
+
+			// 默认展开所有有子节点的成员
+			expandAllNodes() {
+				this.teamData.members.forEach(member => {
+					if (member.hasChildren && member.children && member.children.length > 0) {
+						this.$set(this.expandedNodes, member.id, true);
+					}
+				});
+			},
+
+			refreshData() {
+				uni.showLoading({
+					title: '刷新中...'
+				});
+
+				this.teamData = {
+					level1Count: 0,
+					level2Count: 0,
+					totalBuyCount: 0,
+					totalAmount: '0.00',
+					totalCount: 0,
+					members: []
+				};
+
+				Promise.all([
+					this.getTeamStatistics(),
+					this.getTeamData(),
+					this.getPersonalStats()
+				]).finally(() => {
+					uni.hideLoading();
+					uni.showToast({
+						title: '刷新完成',
+						icon: 'success'
+					});
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.team-container {
+		min-height: 100vh;
+		background: #f8fafc;
+		padding: 20rpx;
+
+		.team-overview {
+			margin-bottom: 20rpx;
+
+			.overview-card {
+				background: #ffffff;
+				border-radius: 16rpx;
+				padding: 30rpx;
+				box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
+				border: 1rpx solid #e5e7eb;
+
+				.card-header {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					margin-bottom: 30rpx;
+					padding-bottom: 20rpx;
+					border-bottom: 1rpx solid #f3f4f6;
+
+					.card-title {
+						font-size: 36rpx;
+						font-weight: 600;
+						color: #111827;
+					}
+				}
+
+				.stats-grid {
+					display: grid;
+					grid-template-columns: repeat(2, 1fr);
+					gap: 20rpx;
+
+					.stat-item {
+						display: flex;
+						align-items: center;
+						padding: 24rpx;
+						border-radius: 16rpx;
+						transition: all 0.3s ease;
+
+						&.primary {
+							background: #f8fafc;
+							border: 1rpx solid #e2e8f0;
+						}
+
+						&.secondary {
+							background: #f1f5f9;
+							border: 1rpx solid #d1d5db;
+						}
+
+						&.success {
+							background: #f0fdf4;
+							border: 1rpx solid #bbf7d0;
+						}
+
+						&.warning {
+							background: #fefce8;
+							border: 1rpx solid #fde047;
+						}
+
+						.stat-icon {
+							margin-right: 16rpx;
+							padding: 12rpx;
+							background: #ffffff;
+							border-radius: 12rpx;
+							box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1);
+						}
+
+						.stat-content {
+							flex: 1;
+
+							.stat-value {
+								font-size: 32rpx;
+								font-weight: 600;
+								color: #111827;
+								margin-bottom: 4rpx;
+								display: block;
+							}
+
+							.stat-label {
+								font-size: 24rpx;
+								color: #6b7280;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		.team-list {
+			background: #ffffff;
+			border-radius: 16rpx;
+			padding: 30rpx;
+			box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
+			border: 1rpx solid #e5e7eb;
+
+			.list-header {
+				display: flex;
+				justify-content: space-between;
+				align-items: flex-start;
+				padding: 0 0 30rpx;
+				border-bottom: 1rpx solid #f3f4f6;
+				margin-bottom: 30rpx;
+
+				.header-left {
+					.header-title {
+						font-size: 36rpx;
+						font-weight: 600;
+						color: #111827;
+					}
+				}
+
+				.header-right {
+					text-align: right;
+
+					.count-display {
+						.total-count {
+							font-size: 28rpx;
+							color: #111827;
+							font-weight: 600;
+							margin-bottom: 8rpx;
+							display: block;
+						}
+
+						.level-indicators {
+							display: flex;
+							flex-direction: column;
+							gap: 6rpx;
+
+							.level-indicator {
+								display: flex;
+								align-items: center;
+								gap: 8rpx;
+								font-size: 22rpx;
+								color: #6b7280;
+
+								.indicator-dot {
+									width: 8rpx;
+									height: 8rpx;
+									border-radius: 50%;
+
+									&.level-1 {
+										background: #3b82f6;
+									}
+
+									&.level-2 {
+										background: #6b7280;
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			.team-tree {
+				padding: 20rpx;
+
+				.tree-node {
+					position: relative;
+					margin-bottom: 16rpx;
+
+					&[data-level="0"] {
+						z-index: 10;
+					}
+
+					&[data-level="1"] {
+						margin-left: 40rpx;
+						z-index: 9;
+					}
+
+					&[data-level="2"] {
+						margin-left: 80rpx;
+						z-index: 8;
+					}
+
+					&.root-node {
+						margin-left: 0;
+
+						.node-content {
+							background: #f8fafc;
+							border-radius: 16rpx;
+							padding: 30rpx;
+							box-shadow: 0 2rpx 12rpx rgba(59, 130, 246, 0.08);
+							transition: all 0.3s ease;
+							border: 1rpx solid #e2e8f0;
+							position: relative;
+							overflow: hidden;
+
+							&:active {
+								transform: scale(0.98);
+							}
+						}
+					}
+
+					.personal-stats {
+						display: flex;
+						flex-wrap: nowrap;
+						gap: 8rpx;
+						margin-top: 8rpx;
+						margin-bottom: 8rpx;
+
+						.stat {
+							font-size: 20rpx;
+							color: #6b7280;
+							background: #f9fafb;
+							padding: 4rpx 10rpx;
+							border-radius: 8rpx;
+							display: flex;
+							align-items: center;
+							gap: 4rpx;
+							white-space: nowrap;
+							border: 1rpx solid #e5e7eb;
+
+							&::before {
+								content: '';
+								width: 4rpx;
+								height: 4rpx;
+								background: #3b82f6;
+								border-radius: 50%;
+								flex-shrink: 0;
+							}
+						}
+					}
+
+					.node-content {
+						display: flex;
+						justify-content: space-between;
+						align-items: flex-start;
+						background: #fff;
+						border-radius: 16rpx;
+						padding: 20rpx;
+						border: 1rpx solid #e5e7eb;
+						transition: all 0.3s ease;
+						position: relative;
+						overflow: hidden;
+						box-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.1);
+
+						&:active {
+							transform: scale(0.98);
+						}
+
+						.node-info {
+							display: flex;
+							align-items: flex-start;
+							flex: 1;
+							min-width: 0;
+
+							.avatar-wrapper {
+								position: relative;
+								margin-right: 16rpx;
+								flex-shrink: 0;
+
+								.avatar {
+									width: 64rpx;
+									height: 64rpx;
+									border-radius: 50%;
+									background-color: #f9fafb;
+									border: 1rpx solid #e5e7eb;
+								}
+
+								.avatar-tag,
+								.root-tag {
+									position: absolute;
+									bottom: -4rpx;
+									right: -4rpx;
+									background: #3b82f6;
+									color: #fff;
+									font-size: 18rpx;
+									padding: 2rpx 8rpx;
+									border-radius: 8rpx;
+									border: 1rpx solid #fff;
+									font-weight: 600;
+								}
+
+								.level-badge {
+									position: absolute;
+									top: -8rpx;
+									left: -8rpx;
+									font-size: 16rpx;
+									padding: 2rpx 6rpx;
+									border-radius: 6rpx;
+									color: #fff;
+									font-weight: bold;
+									border: 1rpx solid #fff;
+
+									&.level-1 {
+										background: #3b82f6;
+									}
+
+									&.level-2 {
+										background: #6b7280;
+									}
+								}
+							}
+
+							.info {
+								flex: 1;
+								min-width: 0;
+
+								.name {
+									font-size: 28rpx;
+									color: #111827;
+									font-weight: 600;
+									margin-bottom: 6rpx;
+									width: 100%;
+									display: block;
+									white-space: nowrap;
+									overflow: hidden;
+									text-overflow: ellipsis;
+									max-width: 300rpx;
+								}
+
+								.level {
+									font-size: 22rpx;
+									color: #6b7280;
+									margin-bottom: 8rpx;
+									display: flex;
+									align-items: center;
+									gap: 6rpx;
+
+									&::before {
+										content: '';
+										width: 6rpx;
+										height: 6rpx;
+										background: #3b82f6;
+										border-radius: 50%;
+										flex-shrink: 0;
+									}
+								}
+
+								.member-stats {
+									display: flex;
+									white-space: nowrap;
+									flex-wrap: nowrap;
+									gap: 8rpx;
+									margin-bottom: 8rpx;
+
+									.stat {
+										white-space: nowrap;
+										flex-wrap: nowrap;
+										font-size: 20rpx;
+										color: #6b7280;
+										background: #f9fafb;
+										padding: 4rpx 10rpx;
+										border-radius: 8rpx;
+										display: flex;
+										align-items: center;
+										gap: 4rpx;
+										white-space: nowrap;
+										border: 1rpx solid #e5e7eb;
+
+										&::before {
+											content: '';
+											width: 4rpx;
+											height: 4rpx;
+											background: #3b82f6;
+											border-radius: 50%;
+											flex-shrink: 0;
+										}
+									}
+								}
+
+								.team-summary {
+									display: flex;
+									gap: 12rpx;
+
+									.summary-text {
+										font-size: 20rpx;
+										color: #6b7280;
+										background: rgba(107, 114, 128, 0.1);
+										padding: 4rpx 10rpx;
+										border-radius: 8rpx;
+										border: 1rpx solid rgba(107, 114, 128, 0.2);
+										font-weight: 500;
+									}
+								}
+							}
+						}
+
+						.node-actions {
+							display: flex;
+							align-items: center;
+							gap: 12rpx;
+							flex-shrink: 0;
+
+							.action-buttons {
+								display: flex;
+								align-items: center;
+								gap: 10rpx;
+
+								.btn-order {
+									display: flex;
+									align-items: center;
+									gap: 4rpx;
+									background: rgba(59, 130, 246, 0.1);
+									padding: 8rpx 12rpx;
+									border-radius: 8rpx;
+									transition: all 0.2s ease;
+									border: 1rpx solid rgba(59, 130, 246, 0.2);
+
+									&:active {
+										transform: scale(0.95);
+										background: rgba(59, 130, 246, 0.15);
+									}
+
+									text {
+										font-size: 20rpx;
+										color: #3b82f6;
+										font-weight: 500;
+									}
+								}
+
+								.member-preview {
+									display: flex;
+									flex-direction: column;
+									align-items: center;
+									text-align: center;
+
+									.preview-label {
+										font-size: 18rpx;
+										color: #6b7280;
+									}
+								}
+							}
+
+							.expand-icon {
+								background: rgba(107, 114, 128, 0.1);
+								padding: 8rpx;
+								border-radius: 8rpx;
+								transition: all 0.3s ease;
+
+								&.expanded {
+									background: rgba(59, 130, 246, 0.1);
+									transform: rotate(180deg);
+								}
+							}
+						}
+					}
+
+					.children {
+						margin-top: 12rpx;
+						position: relative;
+
+						&.level-1-children {
+							padding-left: 20rpx;
+							border-left: 2rpx solid rgba(59, 130, 246, 0.2);
+						}
+
+						&.level-2-children {
+							border-left: 2rpx solid rgba(107, 114, 128, 0.2);
+						}
+
+						&::before {
+							content: '';
+							position: absolute;
+							left: -5rpx;
+							top: 0;
+							width: 8rpx;
+							height: 8rpx;
+							border-radius: 50%;
+							background: rgba(59, 130, 246, 0.4);
+						}
+
+						.level-2-children::before {
+							background: rgba(107, 114, 128, 0.4);
+						}
+					}
+
+					&.level-1-node .node-content {
+						background: #f8fafc;
+						border: 1rpx solid rgba(59, 130, 246, 0.15);
+
+						.avatar-wrapper .avatar {
+							width: 60rpx;
+							height: 60rpx;
+						}
+
+						.info .name {
+							font-size: 26rpx;
+						}
+					}
+
+					&.level-2-node .node-content {
+						background: #f1f5f9;
+						border: 1rpx solid rgba(107, 114, 128, 0.15);
+
+						.avatar-wrapper .avatar {
+							width: 56rpx;
+							height: 56rpx;
+						}
+
+						.info .name {
+							font-size: 24rpx;
+						}
+
+						.member-stats .stat {
+							font-size: 18rpx;
+						}
+					}
+				}
+			}
+		}
+
+		.empty-state {
+			padding: 80rpx 40rpx;
+
+			.empty-content {
+				text-align: center;
+
+				.empty-title {
+					font-size: 32rpx;
+					color: #111827;
+					font-weight: 600;
+					margin-bottom: 16rpx;
+					display: block;
+				}
+
+				.empty-desc {
+					font-size: 26rpx;
+					color: #6b7280;
+					margin-bottom: 40rpx;
+					display: block;
+					line-height: 1.5;
+				}
+
+				.empty-actions {
+					display: flex;
+					justify-content: center;
+					gap: 20rpx;
+
+					.action-button {
+						display: flex;
+						align-items: center;
+						gap: 8rpx;
+						padding: 16rpx 24rpx;
+						border-radius: 12rpx;
+						transition: all 0.3s ease;
+
+						&.primary {
+							background: #3b82f6;
+							color: #fff;
+							box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.2);
+
+							&:active {
+								transform: scale(0.95);
+								box-shadow: 0 1rpx 4rpx rgba(59, 130, 246, 0.2);
+							}
+
+							text {
+								color: #fff;
+								font-size: 24rpx;
+								font-weight: 600;
+							}
+						}
+
+						&.secondary {
+							background: rgba(59, 130, 246, 0.1);
+							border: 1rpx solid rgba(59, 130, 246, 0.2);
+
+							&:active {
+								transform: scale(0.95);
+								background: rgba(59, 130, 246, 0.15);
+							}
+
+							text {
+								color: #3b82f6;
+								font-size: 24rpx;
+								font-weight: 500;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		.flex-items-plus {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+		}
+
+		.empty {
+			width: 200rpx;
+			height: 200rpx;
+		}
+	}
+
+	.btn-order1 {
+		display: flex;
+		align-items: center;
+		gap: 4rpx;
+		background: rgba(59, 130, 246, 0.1);
+		padding: 8rpx 12rpx;
+		border-radius: 8rpx;
+		transition: all 0.2s ease;
+		border: 1rpx solid rgba(59, 130, 246, 0.2);
+
+		&:active {
+			transform: scale(0.95);
+			background: rgba(59, 130, 246, 0.15);
+		}
+
+		text {
+			font-size: 20rpx;
+			color: #3b82f6;
+			font-weight: 500;
+		}
+	}
+</style>

+ 98 - 0
packageUser/pages/user-info/account-change.vue

@@ -0,0 +1,98 @@
+<template>
+	<view class="page-container">
+		<view class="font40 font-bold text-align   pad-t-40 mar-b-50">
+			昵称
+		</view>
+		<view class="inputBg ">
+			<input v-model="user.nickName" placeholder="请输入昵称" />
+			<view class="clear-button" @click="clearInput" v-if=" user.nickName">
+				×
+			</view>
+		</view>
+		<button class="saveBtn" style="background-color: #007aff;" type="primary" @click="save">保存</button>
+	</view>
+</template>
+
+<script>
+	import {
+		memberUpdate,
+		memberDetail
+	} from '@/config/api.js';
+	export default {
+		data() {
+			return {
+				user: {
+					nickName: ""
+				}
+			}
+		},
+		methods: {
+			clearInput() {
+				this.user.nickName = ""
+			},
+			save() {
+				console.log(this.user.nickName)
+				console.log(this.user.account)
+				memberUpdate(this.user).then((res) => {
+					if (res.code == 200) {
+						uni.$u.toast("操作成功")
+						uni.navigateBack({
+							delta: 1 // delta 表示返回的页面数,1 表示返回上一页
+						});
+						uni.setStorageSync('user', null)
+						console.log(12)
+					}
+				})
+			},
+			async detail() {
+				const data = await memberDetail()
+				if (data.code == 200) {
+					this.user = data.data
+				}
+			},
+		},
+		onLoad(op) {
+			console.log(op, "op")
+			this.user = op
+			this.detail()
+		}
+	}
+</script>
+
+<style>
+	.page-container {
+
+		background-color: #FFFFFF;
+		/* 设置页面背景色为白色 */
+		min-height: 100vh;
+		/* 确保背景色覆盖整个可视区域 */
+	}
+
+	.inputBg {
+		background-color: #f3f3f3;
+		width: 700rpx;
+		margin-left: 25rpx;
+		border-radius: 40rpx;
+		font-size: 38rpx;
+		padding: 20rpx;
+		position: relative;
+	}
+
+	.saveBtn {
+		margin-top: 50rpx;
+		width: 700rpx;
+		margin-left: 25rpx;
+		border-radius: 10rpx;
+	}
+
+	.clear-button {
+		position: absolute;
+		right: 10px;
+		top: 50%;
+		transform: translateY(-50%);
+		font-size: 20px;
+		margin-right: 10rpx;
+		color: #999;
+		cursor: pointer;
+	}
+</style>

+ 186 - 0
packageUser/pages/user-info/birthday-change.vue

@@ -0,0 +1,186 @@
+<template>
+	<view class="Date">
+		<view class="content">
+			<!-- 顶部标题 -->
+			<view class="top-title">出生日期</view>
+			<picker-view :value="value" :indicator-style="indicatorStyle" @change="bindChange" class="picker-view">
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in years" :key="index">{{item}}年</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in months" :key="index">{{item}}月</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in days" :key="index">{{item}}日</view>
+				</picker-view-column>
+			</picker-view>
+			<!-- 底部保存按钮 -->
+		</view>
+		<button class="save-button" @click="ack">保存</button>
+	</view>
+</template>
+
+<script>
+	import {
+		memberUpdate,
+		memberDetail
+	} from '@/config/api.js';
+	export default {
+		props: {
+			selectedDate: {
+				type: String,
+				default: "",
+			},
+		},
+		data() {
+			return {
+				user: {},
+				years: [],
+				year: '',
+				months: [],
+				month: '',
+				days: [],
+				day: '',
+				value: [],
+				indicatorStyle: 'height: 50px;'
+			}
+		},
+		mounted() {
+			const date = new Date();
+			const year = date.getFullYear();
+			const month = date.getMonth() + 1;
+			const day = date.getDate();
+			const selectedYear = new Date(this.selectedDate).getFullYear() || year;
+			const selectedMonth = new Date(this.selectedDate).getMonth() + 1 || month;
+			const selectedDay = new Date(this.selectedDate).getDate() || day;
+			setTimeout(() => {
+				this.value = [this.years.indexOf(selectedYear), selectedMonth - 1, selectedDay - 1];
+				this.year = selectedYear;
+				this.month = selectedMonth < 10 ? `0${selectedMonth}` : selectedMonth;
+				this.day = selectedDay < 10 ? `0${selectedDay}` : selectedDay;
+			}, 0);
+		},
+		created() {
+			this.init();
+		},
+		onLoad(op) {
+			this.detail();
+		},
+		methods: {
+			async detail() {
+				const data = await memberDetail();
+				if (data.code == 200) {
+					this.user = data.data;
+				}
+			},
+			init() {
+				const date = new Date();
+				const year = date.getFullYear();
+				const month = date.getMonth() + 1;
+				const day = date.getDate();
+
+				// 生成年份范围(例如从 1900 年到当前年份)
+				const years = [];
+				const startYear = 1900; // 你可以根据需要调整起始年份
+				for (let i = startYear; i <= year; i++) {
+					years.push(i);
+				}
+
+				// 生成月份范围(1 到 12)
+				const months = [];
+				for (let i = 1; i <= 12; i++) {
+					months.push(i);
+				}
+
+				// 更新数据
+				this.years = years;
+				this.months = months;
+				this.getDays(year, month);
+			},
+			getDays(year, month) {
+				const day = new Date(year, month, 0).getDate();
+				const days = [];
+				for (let i = 1; i <= day; i++) {
+					days.push(i);
+				}
+				this.days = days;
+			},
+			bindChange: function(e) {
+				const val = e.detail.value;
+				this.year = this.years[val[0]];
+				this.month = this.months[val[1]];
+				this.day = this.days[val[2]];
+
+				// 切换年份或月份时,更新天数
+				this.getDays(this.year, this.month);
+			},
+			esc() {
+				this.$emit('popType', false);
+			},
+			ack() {
+				const date = new Date(this.year, this.month - 1, this.day);
+				const formattedDate = `${this.year}-${this.month}-${this.day}`; // 手动格式化日期
+				console.log(formattedDate);
+				this.user.birthday = formattedDate;
+				memberUpdate(this.user).then((res) => {
+					if (res.code == 200) {
+						uni.$u.toast("操作成功");
+						uni.navigateBack({
+							delta: 1 // delta 表示返回的页面数,1 表示返回上一页
+						});
+						uni.setStorageSync('user', null)
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.Date {
+		background-color: white;
+		padding: 30px;
+		min-height: 100vh;
+
+		.content {
+			width: 100%;
+			/* 内容区域宽度 */
+			background: #FFFFFF;
+			padding: 20px;
+			display: flex;
+			flex-direction: column;
+
+			.top-title {
+				text-align: center;
+				font-size: 40rpx;
+				font-weight: bold;
+				margin-bottom: 20px;
+			}
+
+			.picker-view {
+				width: 100%;
+				height: 200px;
+				/* 选择器高度 */
+				margin-bottom: 20px;
+			}
+
+			.item {
+				line-height: 30px;
+				font-size: 32rpx;
+				text-align: center;
+			}
+
+
+		}
+	}
+
+	.save-button {
+		background-color: #007aff;
+		color: white;
+		margin-top: 50rpx;
+		border: none;
+		border-radius: 10rpx;
+		padding: 10rpx;
+		font-size: 16px;
+	}
+</style>

+ 323 - 0
packageUser/pages/user-info/index.vue

@@ -0,0 +1,323 @@
+<template>
+	<view>
+		<!-- 头像区域 -->
+		<view class="avatar-region">
+			<view class="flex-items">
+				<view class="font32 mar-l-20">
+					头像
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<u-upload :fileList="fileList" @afterRead="afterRead" @delete="deletePic" name="1" :maxCount="1">
+						<view class="flex-items">
+							<image class="avatar" :src="user.avatar||'../../../static/images/avatar.png'"
+								mode="aspectFill">
+							</image>
+							<uni-icons color="#d0d7df" type="right" size="30"></uni-icons>
+						</view>
+					</u-upload>
+				</view>
+			</view>
+		</view>
+		<!-- 基本信息区域 -->
+		<view class="info-region">
+			<view class="font-gray mar-l-20 mar-b-30">
+				基本信息
+			</view>
+			<view class="flex-items mar-b-50">
+				<view class="font32 mar-l-20">
+					昵称
+				</view>
+				<view class="mar-l-auto mar-r-20" @click="changeAccount">
+					<view class="flex-items">
+						<view class="font32 font-gray">
+							{{user.account}}
+						</view>
+						<view>
+							<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="flex-items mar-b-50" @click="changeBirthday">
+				<view class="font32 mar-l-20 ">
+					生日
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+						<view class="font32 font-gray">
+							{{user.birthday}}
+						</view>
+						<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+					</view>
+				</view>
+			</view>
+			<view class="flex-items mar-b-50" @click="changeSex">
+				<view class="font32 mar-l-20 ">
+					性别
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+						<view class="font32 font-gray" v-if="user.sex==0">
+							女
+						</view>
+						<view class="font32  font-gray" v-if="user.sex==1">
+							男
+						</view>
+						<view class="font32 font-gray " v-if="user.sex==2">
+							其他
+						</view>
+						<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+					</view>
+				</view>
+			</view>
+			<view class="flex-items mar-b-50">
+				<view class="font32 mar-l-20 ">
+					上级昵称
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+
+
+						<view class="font32 font-gray ">
+							{{user.parentAccount||'-'}}
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="flex-items mar-b-50" @tap="goLevelPath">
+				<view class="font32 mar-l-20 ">
+					会员等级
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+
+
+						<view class="font32 font-gray ">
+							<text v-if="user.level == '0'">普通会员</text>
+							<text v-else-if="user.level == '1'">S级会员</text>
+							<text v-else-if="user.level == '2'">SS级会员</text>
+							<text v-else-if="user.level == '3'">SSS级会员</text>
+							<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="flex-items mar-b-50" @tap="goAgentPath">
+				<view class="font32 mar-l-20 ">
+					代理等级
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+						<view class="font32 font-gray ">
+							<text v-if="user.agent == 2">荣耀代理</text>
+							<text v-else-if="user.agent == 3">超凡代理</text>
+							<text v-else>无</text>
+							<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- <view class="flex-items mar-b-20">
+				<view class="font32 mar-l-20 ">
+					国家/地区
+				</view>
+				<view class="mar-l-auto mar-r-20">
+					<view class="flex-items">
+						<view class="font32 font-gray">
+							{{user.nation}}
+						</view>
+						<uni-icons color="#d0d7df" type="right" size="20"></uni-icons>
+					</view>
+				</view>
+			</view> -->
+
+		</view>
+		<view class=" flex-center">
+			<!-- <button class="loginBtn" style="background-color: #3C86F7;" type="primary"
+				@click="handlerLogout">退出登录</button> -->
+			<button @click="handlerLogout" class="logout-button">
+				退出登录
+			</button>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import {
+		memberDetail,
+		memberUpdate
+	} from '@/config/api';
+
+	import {
+		mapMutations,
+		mapActions,
+		mapGetters
+	} from 'vuex';
+	import {
+		UPLOAD_URL
+	} from '@/common/config.js'
+	export default {
+		data() {
+			return {
+				fileList: [],
+				user: {},
+				token: "",
+			};
+		},
+		computed: {
+			...mapGetters(['userInfo'])
+		},
+		onLoad() {
+			this.token = uni.getStorageSync('access_token')
+		},
+		onShow() {
+			this.detail()
+			console.log(this.user)
+		},
+
+		methods: {
+			...mapActions(['logout']),
+			goAgentPath() {
+				uni.showModal({
+					title: '升级/续费代理',
+					content: '购买代理套餐即可升级或续费代理,\n立即前往?',
+					success: (res) => {
+						if (res.confirm) {
+							// 这里可以跳转到代理升级页面
+							this.$route('/packageShop/pages/search/product-list?serachKey=' + "代理")
+						}
+					}
+				});
+			},
+			goLevelPath() {
+				this.$route('/packageUser/pages/level/index')
+			},
+			handlerLogout() {
+				this.logout()
+				uni.$u.toast("退出登录")
+				uni.navigateBack({
+					delta: 1 // delta 表示返回的页面数,1 表示返回上一页
+				});
+			},
+			changeSex() {
+				this.$route('/packageUser/pages/user-info/sex-change?id=' + this.user.sex + '&id=' + this.user.id)
+			},
+
+			changeAccount() {
+				this.$route('/packageUser/pages/user-info/account-change?id=' + this.user.account + '&id=' + this.user.id)
+			},
+			changeBirthday() {
+				this.$route('/packageUser/pages/user-info/birthday-change?account=' + this.user.birthdat + '&id=' + this
+					.user.id)
+			},
+			// 删除图片
+			deletePic(event) {
+				this.fileList.splice(event.index, 1)
+			},
+			// 新增图片
+			async afterRead(event) {
+				// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
+				let lists = [].concat(event.file)
+				console.log(lists, "list")
+				let fileListLen = this.fileList.length
+				for (let i = 0; i < lists.length; i++) {
+					const result = await this.uploadFilePromise(lists[i].thumb)
+					this.user.avatar = result.data.link
+					let item = this.fileList[fileListLen]
+				}
+				memberUpdate(this.user)
+			},
+			uploadFilePromise(url) {
+				return new Promise((resolve, reject) => {
+					let a = uni.uploadFile({
+						url: UPLOAD_URL,
+						filePath: url,
+						header: {
+							"Blade-Auth": this.token
+						},
+						name: 'file',
+						formData: {
+							user: 'test'
+						},
+						success: (res) => {
+							resolve(JSON.parse(res.data))
+						}
+					});
+				})
+			},
+			async detail() {
+				const data = await memberDetail()
+				if (data.code == 200) {
+					this.user = data.data
+					uni.setStorageSync('user', this.user)
+				}
+			},
+			goPage() {
+				// this.$Router.push({ path: '/pages/user/set-avatar' });
+			}
+		},
+
+	};
+</script>
+
+<style>
+	.arrow {
+		width: 32rpx;
+		height: 32rpx;
+		margin-top: 13rpx;
+	}
+
+	.h28 {
+		height: 56rpx;
+	}
+
+	.avatar-region {
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		border-radius: 40rpx;
+		width: 700rpx;
+		height: auto;
+		padding-bottom: 30rpx;
+		padding-top: 30rpx;
+		background-color: white;
+		margin-top: 30rpx;
+		margin-bottom: 30rpx;
+		margin-left: 25rpx;
+	}
+
+	.info-region {
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		border-radius: 40rpx;
+		width: 700rpx;
+		height: auto;
+		padding-bottom: 30rpx;
+		padding-top: 30rpx;
+		background-color: white;
+		margin-top: 30rpx;
+		margin-bottom: 30rpx;
+		margin-left: 25rpx;
+	}
+
+	.avatar-container {
+		margin-left: 50rpx;
+		margin-right: 50rpx;
+	}
+
+	.avatar {
+		width: 130rpx;
+		height: 130rpx;
+		border-radius: 50%;
+		overflow: hidden;
+	}
+
+	.logout-button {
+		width: 686rpx;
+		height: 88rpx;
+		background: #FF4748;
+		border-radius: 44rpx;
+		font-size: 32rpx;
+		color: #FFFFFF;
+		margin-top: 100rpx;
+	}
+</style>

+ 118 - 0
packageUser/pages/user-info/sex-change.vue

@@ -0,0 +1,118 @@
+<template>
+	<view class="gender-selection-page">
+		<!-- 页面标题 -->
+		<view class="title">请选择您的性别</view>
+		<!-- 性别选项 -->
+		<view class="gender-options">
+			<view class="gender-option" :class="{ 'selected': selectedGender === 1}" @click="selectGender(1)">
+				男性
+			</view>
+			<view class="gender-option" :class="{ 'selected': selectedGender ===0 }" @click="selectGender(0)">
+				女性
+			</view>
+
+		</view>
+		<!-- 确认按钮 -->
+		<button class="save-button" @click="ack">保存</button>
+	</view>
+</template>
+
+<script>
+	import {
+		memberUpdate,
+		memberDetail
+	} from '@/config/api.js';
+	export default {
+		data() {
+			return {
+				// 存储选中的性别,初始值为空
+				selectedGender: '',
+				user: {}
+			};
+		},
+		onLoad(op) {
+			this.detail()
+
+		},
+		methods: {
+			async detail() {
+				const data = await memberDetail()
+				if (data.code == 200) {
+					this.user = data.data
+					this.selectedGender = this.user.sex
+				}
+			},
+			// 选择性别
+			selectGender(gender) {
+				this.selectedGender = gender;
+			},
+			ack() {
+				this.user.sex = this.selectedGender
+				memberUpdate(this.user).then((res) => {
+					if (res.code == 200) {
+						uni.$u.toast("操作成功")
+						uni.navigateBack({
+							delta: 1 // delta 表示返回的页面数,1 表示返回上一页
+						});
+						uni.setStorageSync('user', null)
+						console.log(12)
+					}
+				})
+			},
+
+		}
+	};
+</script>
+
+<style scoped>
+	.gender-selection-page {
+		padding: 30px;
+		background-color: white;
+		min-height: 100vh;
+		text-align: center;
+	}
+
+	.title {
+		font-size: 20px;
+		font-weight: bold;
+		margin-bottom: 20px;
+	}
+
+	.gender-options {
+		display: flex;
+		justify-content: center;
+		margin-bottom: 20px;
+		flex-wrap: nowrap;
+	}
+
+	.gender-option {
+		width: 100px;
+		padding: 10px 20px;
+		border: 1px solid #ccc;
+		margin: 0 10px;
+		cursor: pointer;
+		border-radius: 5px;
+		box-sizing: border-box;
+		/* 明确设置高度 */
+		height: 40px;
+		line-height: 20px;
+		/* 使文字垂直居中 */
+	}
+
+	.gender-option.selected {
+		background-color: #007aff;
+		color: white;
+		border-color: #007aff;
+		/* 确保边框颜色与背景颜色一致 */
+	}
+
+	.save-button {
+		background-color: #007aff;
+		color: white;
+		margin-top: 50rpx;
+		border: none;
+		border-radius: 10rpx;
+		padding: 10rpx;
+		font-size: 16px;
+	}
+</style>

+ 257 - 0
pages.json

@@ -0,0 +1,257 @@
+{
+	"easycom": {
+		"autoscan": true,
+		"custom": {
+			"^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
+			"^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
+			"^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
+			"^CustomToast$": "@/components/CustomToast.vue"
+		}
+	},
+
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/indexNew",
+			"style": {
+				"navigationBarTitleText": "首页",
+				"navigationStyle": "custom",
+				"navigationBarBackgroundColor": "#ffffff",
+				"enablePullDownRefresh": true
+			}
+		},
+		{
+			"path": "pages/tabbar/my",
+			"style": {
+				"navigationBarTitleText": "个人中心"
+			},
+			"meta": {
+				"auth": false
+			}
+		},
+		{
+			"path": "pages/shop/product-type-list",
+			"style": {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTitleText": "全部商品"
+			}
+		}
+	],
+	"tabBar": {
+		"color": "#999999",
+		"selectedColor": "#292C35",
+		"borderStyle": "white",
+		"backgroundColor": "#FFFFFF",
+		"list": [
+			// {
+			// 	"pagePath": "pages/index/index-copy",
+			// 	"text": "%index.home%"
+			// },
+			{
+				"pagePath": "pages/index/indexNew",
+				"iconPath": "/static/tabbar/home.png",
+				"selectedIconPath": "/static/tabbar/home-select.png",
+				"text": "首页"
+			},
+			{
+				"pagePath": "pages/shop/product-type-list",
+				"iconPath": "/static/tabbar/shop.png",
+				"selectedIconPath": "/static/tabbar/shop-select.png",
+				"text": "分类"
+			},
+			{
+				"pagePath": "pages/tabbar/my",
+				"iconPath": "/static/tabbar/my.png",
+				"selectedIconPath": "/static/tabbar/my-select.png",
+				"text": "个人中心"
+			}
+		]
+	},
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"subPackages": [{
+			"root": "packageUser",
+			"pages": [{
+					"path": "pages/login/index",
+					"style": {
+						"navigationBarTitleText": "登录",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "pages/register/index",
+					"style": {
+						"navigationBarTitleText": "注册"
+					}
+				},
+				{
+					"path": "pages/collect/index",
+					"style": {
+						"navigationBarTitleText": "收藏"
+					}
+				},
+				{
+					"path": "pages/level/index",
+					"style": {
+						"navigationBarTitleText": "等级中心"
+					}
+				},
+				{
+					"path": "pages/team/index",
+					"style": {
+						"navigationBarTitleText": "我的团队"
+					}
+				},
+				{
+					"path": "pages/user-info/index",
+					"style": {
+						"navigationBarTitleText": "个人信息"
+					}
+				},
+				{
+					"path": "pages/user-info/account-change",
+					"style": {
+						"navigationBarTitleText": "修改昵称"
+					}
+				},
+				{
+					"path": "pages/share/index",
+					"style": {
+						"navigationBarTitleText": "邀请"
+					}
+				},
+				{
+					"path": "pages/user-info/sex-change",
+					"style": {
+						"navigationBarTitleText": "修改性别"
+					}
+				},
+				{
+					"path": "pages/user-info/birthday-change",
+					"style": {
+						"navigationBarTitleText": "修改生日"
+					}
+				},
+				{
+					"path": "pages/richtext/index",
+					"style": {
+						"navigationBarTitleText": "协议"
+					}
+				}
+
+			]
+		},
+		{
+			"root": "packageShop",
+			"pages": [{
+					"path": "pages/cart/index",
+					"style": {
+						"navigationBarTitleText": "购物车"
+					}
+				},
+				{
+					"path": "pages/search/index",
+					"style": {
+						"navigationBarTitleText": "搜索"
+					}
+				},
+				{
+					"path": "pages/search/product-list",
+					"style": {
+						"navigationBarTitleText": "商品列表"
+					}
+				},
+				{
+					"path": "pages/article/detail",
+					"style": {
+						"navigationBarTitleText": "文章详情"
+					}
+				},
+
+				{
+					"path": "pages/detail/index",
+					"style": {
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "pages/settle/index",
+					"style": {
+						"navigationBarTitleText": "确认订单"
+					}
+				}
+			]
+		},
+		{
+			"root": "packageOrder",
+			"pages": [{
+					"path": "pages/list/index",
+					"style": {
+						"navigationBarTitleText": "订单列表",
+						"enablePullDownRefresh": true
+					}
+				}, {
+					"path": "pages/list/record",
+					"style": {
+						"navigationBarTitleText": "订单记录",
+						"enablePullDownRefresh": true
+					}
+				},
+				{
+					"path": "pages/score/index",
+					"style": {
+						"navigationBarTitleText": "商品评价"
+					}
+				},
+				{
+					"path": "pages/score/product-score",
+					"style": {
+						"navigationBarTitleText": "商品评价"
+					}
+				},
+				{
+					"path": "pages/detail/index",
+					"style": {
+						"navigationBarTitleText": "订单详情"
+					}
+				},
+				{
+					"path": "pages/payment/index",
+					"style": {
+						"navigationBarTitleText": "订单支付"
+					}
+				},
+				{
+					"path": "pages/logistics/index",
+					"style": {
+						"navigationBarTitleText": "物流查询"
+					}
+				},
+				{
+					"path": "pages/after-sale/index",
+					"style": {
+						"navigationBarTitleText": "售后服务"
+					}
+				}
+			]
+		}
+
+	],
+	"preloadRule": {
+		"pages/index/indexNew": {
+			"network": "all",
+			"packages": ["packageShop"]
+		},
+		"pages/shop/product-type-list": {
+			"network": "all",
+			"packages": ["packageShop", "packageOrder"]
+		},
+		"pages/tabbar/my": {
+			"network": "all",
+			"packages": ["packageUser"]
+		}
+	}
+}

+ 2454 - 0
pages/index/indexNew.vue

@@ -0,0 +1,2454 @@
+<template>
+	<view class="container">
+		<!-- 固定顶部导航栏 -->
+		<view class="fixed-header" :class="{'show-fixed': showFixed}">
+			<view class="status_bar" :style="{height: statusBarHeight + 'px'}">
+				<view class="top_view"></view>
+			</view>
+			<view class="nav-content">
+				<view class="left-section">
+					<view class="logo-container">
+						<image class="logo-image"
+							src="https://ndtk.tos-cn-guangzhou.volces.com/uploads/156adea827104e38ae0e25ec4701ecfe.jpg"
+							mode="aspectFit"></image>
+						<text class="logo-text">宏匠</text>
+					</view>
+				</view>
+				<view class="right-section">
+					<view class="fixed-search-bar" @click="goSearch">
+						<view class="search-icon-wrapper">
+							<image class="search-icon" src="/static/images/search.png"></image>
+						</view>
+						<view class="search-input">搜索</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 原始顶部导航栏 -->
+		<view class="header">
+			<view class="status_bar" :style="{height: statusBarHeight + 'px'}">
+				<view class="top_view"></view>
+			</view>
+			<view class="header-content">
+				<view class="logo-section">
+					<view class="logo-container">
+						<image class="logo-image"
+							src="https://ndtk.tos-cn-guangzhou.volces.com/uploads/156adea827104e38ae0e25ec4701ecfe.jpg"
+							mode="aspectFit"></image>
+						<text class="logo-text">宏匠</text>
+					</view>
+					<view class="sub-title">传承千年唐卡艺术</view>
+				</view>
+				<view class="search-bar" @click="goSearch">
+					<view class="search-icon-wrapper">
+						<image class="search-icon" src="/static/images/search.png"></image>
+					</view>
+					<view class="search-input">寻找心仪的唐卡</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 轮播图 -->
+		<view class="swiper-section">
+			<view class="swiper-container">
+				<swiper class="swiper" :indicator-dots="true" :autoplay="true" :interval="4000" :duration="800"
+					indicator-color="rgba(139,69,19,0.3)" indicator-active-color="#8B4513">
+					<swiper-item v-for="(item, index) in bannerList" :key="index" @click="handleBannerClick(item)">
+						<image class="swiper-image" :src="item.image" mode="aspectFill"></image>
+					</swiper-item>
+				</swiper>
+			</view>
+		</view>
+
+		<!-- 爆款产品 -->
+		<view class="hot-section">
+			<view class="hot-header">
+				<text class="hot-title">🔥 爆款唐卡</text>
+				<view class="hot-decoration"></view>
+			</view>
+			<view class="hot-grid">
+				<view class="hot-item" v-for="(item, index) in menuList" :key="index" @click="goCategory(item)">
+					<view class="hot-icon-wrapper">
+						<image class="hot-icon" :src="item.image" mode="aspectFit"></image>
+						<view class="hot-badge">热</view>
+						<view class="hot-glow"></view>
+					</view>
+					<text class="hot-name">{{item.title}}</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 成为代理商品 -->
+		<view class="product-section agent-section">
+			<view class="section-header">
+				<view class="section-title">
+					<text class="title-text">代理专享</text>
+					<view class="title-decoration">
+						<view class="decoration-line"></view>
+						<image class="decoration-symbol" src="/static/images/zhuwang.png" mode="aspectFit"></image>
+						<view class="decoration-line"></view>
+					</view>
+				</view>
+				<view class="section-subtitle">传承千年工艺,开启艺术创业之路</view>
+			</view>
+
+			<view class="product-list">
+				<view class="product-item agent-item" v-for="(item, index) in agentProducts" :key="index"
+					@click="goDetail(item.id)">
+					<view class="product-image-container">
+						<image class="product-image" :src="item.images" mode="aspectFill"></image>
+						<view class="agent-badge">代理专享</view>
+					</view>
+					<view class="product-content">
+						<view class="product-name">{{item.name}}</view>
+						<view class="product-meta">
+							<view class="update-time">{{item.spec}}</view>
+							<view class="sold-count">已售{{item.soldCount}}件</view>
+						</view>
+						<view class="product-footer">
+							<view class="price-section">
+								<text class="price-symbol">¥</text>
+								<text class="price-value">{{item.price}}</text>
+							</view>
+							<view class="buy-btn agent-btn" @click.stop="goDetail(item.id)">
+								<text class="buy-text">申请代理</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 精选商品 -->
+		<view class="product-section selected-section">
+			<view class="section-header">
+				<view class="section-title">
+					<text class="title-text">精选唐卡</text>
+					<view class="title-decoration">
+						<view class="decoration-line"></view>
+						<image class="decoration-symbol" src="/static/images/zhuwang.png" mode="aspectFit"></image>
+						<view class="decoration-line"></view>
+					</view>
+				</view>
+				<view class="section-subtitle">匠心之作,艺术臻品</view>
+			</view>
+
+			<view class="selected-product-grid">
+				<view class="selected-product-card" v-for="(item, index) in selectedProducts" :key="index"
+					@click="goDetail(item.id)">
+					<view class="selected-product-image-wrapper">
+						<image class="selected-product-image" :src="item.images" mode="aspectFill"></image>
+						<view class="selected-product-badge" v-if="item.soldCount > 100">热销</view>
+					</view>
+					<view class="selected-product-info">
+						<view class="selected-product-name">{{item.name}}</view>
+						<view class="selected-product-desc">{{item.spec}}</view>
+						<view class="selected-product-meta">
+							<view class="rating">
+								<text class="rating-star">★</text>
+								<text class="rating-value">{{ item.productAvg.toFixed(1) }}</text>
+							</view>
+							<view class="selected-sold-count">已售{{item.soldCount}}件</view>
+						</view>
+						<view class="selected-product-footer">
+							<view class="selected-price-section">
+								<text class="selected-price-symbol">¥</text>
+								<text class="selected-price-value">{{item.price}}</text>
+							</view>
+							<view class="selected-buy-btn" @click.stop="goDetail(item.id)">
+								<text class="selected-buy-text">立即购买</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 加载更多按钮 -->
+			<view class="load-more-section" v-if="selectedHasMore && selectedProducts.length > 0">
+				<view class="load-more-btn" @click="loadMoreSelectedProducts" :class="{'loading': isLoading}">
+					<text v-if="!isLoading">查看更多</text>
+					<text v-else>加载中...</text>
+				</view>
+			</view>
+
+			<!-- 没有更多数据提示 -->
+			<view class="no-more-tip" v-if="!selectedHasMore && selectedProducts.length > 0">
+				<text>~ 已经到底啦 ~</text>
+			</view>
+		</view>
+
+		<!-- 底部装饰 -->
+		<view class="bottom-decoration">
+			<view class="decoration-pattern"></view>
+		</view>
+
+		<!-- 邀请弹框 -->
+		<view class="invitation-modal" v-if="showInvitationModal" @click="closeInvitationModal">
+			<view class="modal-content" @click.stop="">
+				<view class="modal-header">
+					<text class="modal-title">🎉 邀请通知</text>
+					<view class="close-btn" @click="closeInvitationModal">✕</view>
+				</view>
+
+				<view class="modal-body">
+					<view class="inviter-info">
+						<image class="inviter-avatar" :src="inviterInfo.avatar || '/static/images/avatar.png'"
+							mode="aspectFill"></image>
+						<view class="inviter-details">
+							<text class="inviter-name">{{ inviterInfo.account || '好友' }}</text>
+							<text class="inviter-desc">邀请您成为下级用户</text>
+						</view>
+					</view>
+
+					<view class="invitation-desc">
+						<text class="desc-text" v-if="isLogin">接受邀请后,您将成为该用户的下级。</text>
+						<text class="desc-text" v-else>接受邀请后,您将成为该用户的下级。请先登录以完成邀请接受。</text>
+					</view>
+				</view>
+
+				<view class="modal-footer">
+					<button class="reject-btn" @click="rejectInvitation">拒绝</button>
+					<button class="accept-btn" @click="acceptInvitation">
+						<text v-if="isLogin">接受邀请</text>
+						<text v-else>登录并接受</text>
+					</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		carouselQueryAll,
+		wxLogin,
+		getIndexMenu,
+		getNewProducts,
+		getAgentProducts,
+		getSelectedProducts,
+		userLogin,
+		getMemberDetail,
+		getIndexMemberDetail,
+		acceptInvitation,
+		rejectInvitation,
+		getPendingInvitations
+	} from '@/config/api.js';
+	import {
+		mapGetters,
+		mapActions
+	} from 'vuex';
+	import {
+		shareImg
+	} from '@/common/config.js'
+	export default {
+		data() {
+			return {
+				bannerList: [],
+				menuList: [],
+				agentProducts: [], // 代理商品列表
+				selectedProducts: [], // 精选商品列表
+				selectedParams: {
+					current: 1,
+					size: 6,
+					name: ''
+				},
+				selectedTotal: 0, // 精选商品总数
+				selectedHasMore: true, // 是否还有更多精选商品
+				params: {
+					current: 1,
+					size: 10,
+					name: '',
+					sort: '',
+					arrow: ''
+				},
+				isLoading: false, // 是否正在加载
+				noMoreData: false, // 是否没有更多数据
+				showFixed: false, // 是否显示固定顶部
+				scrollTop: 0, // 当前滚动位置
+				statusBarHeight: 0,
+				// 邀请相关
+				showInvitationModal: false, // 是否显示邀请弹框
+				inviterInfo: {}, // 邀请人信息
+				inviterId: null, // 邀请人ID
+				hasProcessedUrlInvitation: false, // 是否已处理URL中的邀请
+			}
+		},
+		computed: {
+			...mapGetters(['isLogin'])
+		},
+		onLoad(options) {
+			// 获取状态栏高度
+			const systemInfo = uni.getSystemInfoSync();
+			this.statusBarHeight = systemInfo.statusBarHeight;
+			// 计算导航栏总高度 (状态栏 + 导航栏实际高度)
+			this.navTotalHeight = this.statusBarHeight + 90; // 状态栏 + 导航栏高度
+
+			// 处理邀请参数
+			console.log('页面加载参数:', options);
+			if (options.inviter) {
+				console.log('发现邀请人ID:', options.inviter);
+				this.inviterId = options.inviter;
+			}
+
+			this.refresh();
+		},
+		// 微信小程序分享配置
+		onShareAppMessage() {
+			return {
+				title: `邀请您加入宏匠唐卡`,
+				path: `/pages/index/indexNew`,
+				imageUrl: shareImg // 分享图片,需要添加
+			}
+		},
+		onShareTimeline(res) {
+			let that = this;
+			let shareInfo = store.state.vuex_shareInfo;
+			let query = shareInfo.query;
+			//携带当前页面资源ID参数
+			let currentPage = getCurrentPages()[getCurrentPages().length - 1];
+			let options = currentPage.options;
+			if (JSON.stringify(options) != '{}' && options.id) {
+				query += `&id=${options.id}`;
+			}
+
+			return {
+				title: shareInfo.title,
+				query: query,
+				imageUrl: shareImg,
+				success(res) {
+					uni.showToast({
+						title: '分享成功'
+					})
+				},
+				fail(res) {
+					uni.showToast({
+						title: '分享失败',
+						icon: 'none'
+					})
+				},
+				complete() {}
+			}
+		},
+		onShow() {
+			console.log('onShow 触发,当前 inviterId =', this.inviterId);
+			// #ifdef H5
+			this.login()
+			// #endif
+
+			// 检查是否有邀请
+			this.checkInvitation();
+		},
+
+		onReady() {
+			console.log('onReady 触发,当前 inviterId =', this.inviterId);
+			// 页面渲染完成后也检查一次邀请,确保弹框能正常显示
+			setTimeout(() => {
+				console.log('onReady 延时触发 checkInvitation,当前 inviterId =', this.inviterId);
+				this.checkInvitation();
+			}, 500);
+		},
+		onPullDownRefresh() {
+			this.refresh()
+			uni.stopPullDownRefresh();
+		},
+		onPageScroll(e) {
+			// 获取当前滚动位置
+			this.scrollTop = e.scrollTop;
+			// 当滚动超过200时显示固定顶部
+			this.showFixed = this.scrollTop > 200;
+		},
+
+		methods: {
+
+			...mapActions(['setToken', 'setUserInfo']),
+			refresh() {
+				this.getShufflingList();
+				this.getMenuList();
+				this.getProductList();
+			},
+			// 获取菜单列表
+			getMenuList() {
+				getIndexMenu().then(res => {
+					if (res.code === 200) {
+						// 按sort字段排序
+						console.log(res.data)
+						this.menuList = res.data.sort((a, b) => a.sort - b.sort);
+					}
+				})
+			},
+
+			getShufflingList() {
+				carouselQueryAll().then(res => {
+					if (res.code === 200) {
+						this.bannerList = res.data;
+					}
+				});
+			},
+
+			goSearch() {
+				uni.navigateTo({
+					url: '/packageShop/pages/search/index?type=1'
+				});
+			},
+			login() {
+				let params = {
+					username: "USER082927",
+					grant_type: "web",
+					memberId: "1957060037088083973"
+
+				}
+				userLogin(params).then((loginData) => {
+					uni.setStorageSync('access_token', loginData.access_token);
+					uni.setStorageSync('refresh_token', loginData.refresh_token);
+					uni.setStorageSync('user', loginData);
+
+					this.$store.commit('isLogin', true);
+					this.$store.commit('refresh_token', loginData.refresh_token);
+				})
+			},
+			goCategory(item) {
+				if (!item) return;
+
+				switch (item.dataType) {
+					case 0: // 普通页面路径
+						if (item.path) {
+							uni.navigateTo({
+								url: item.path
+							});
+						}
+						break;
+
+					case 1: // 文章内容
+						if (item.id) {
+							uni.navigateTo({
+								url: '/packageShop/pages/article/detail?id=' + item.id
+							});
+						}
+						break;
+
+					case 2: // 底部导航栏路径
+						if (item.path) {
+							uni.switchTab({
+								url: item.path
+							});
+						}
+						break;
+
+					default:
+						console.log('未知的跳转类型');
+				}
+			},
+
+			handleBannerClick(item) {
+				if (item.path) {
+					uni.navigateTo({
+						url: item.path
+					});
+				}
+			},
+
+			handleLogin() {
+				uni.navigateTo({
+					url: '/packageUser/pages/login/index'
+				});
+			},
+
+			/**
+			 * 加载代理商品列表
+			 */
+			loadAgentProducts() {
+				getAgentProducts({}).then(res => {
+					if (res.code === 200) {
+						// 处理代理商品数据
+						this.agentProducts = res.data.filter(item => item.shelfLife === 1).map(item => ({
+							id: item.id,
+							images: item.images,
+							name: item.name,
+							spec: `库存:${item.stock}`,
+							updateTime: item.updateTime.split(' ')[0].replace(/-/g, '月').substring(5) +
+								'日',
+							price: item.price,
+							soldCount: item.salesTotal,
+							productType: item.productType,
+							productAvg: parseFloat(item.productAvg) || 0
+						}));
+					} else {
+						uni.showToast({
+							title: res.msg || '获取代理商品失败',
+							icon: 'none'
+						});
+					}
+				}).catch(err => {
+					console.error('获取代理商品失败:', err);
+					uni.showToast({
+						title: '获取代理商品失败',
+						icon: 'none'
+					});
+				});
+			},
+
+			/**
+			 * 加载精选唐卡商品列表
+			 */
+			loadSelectedProducts(loadMore = false) {
+				if (!loadMore) {
+					uni.showLoading({
+						title: '加载中...'
+					});
+				}
+
+				getSelectedProducts(this.selectedParams).then(res => {
+					if (res.code === 200) {
+						const productList = res.data.records.filter(item => item.shelfLife === 1).map(item => ({
+							id: item.id,
+							images: item.images,
+							name: item.name,
+							spec: `库存:${item.stock}`,
+							updateTime: item.updateTime.split(' ')[0].replace(/-/g, '月').substring(5) +
+								'日',
+							price: item.price,
+							soldCount: item.salesTotal,
+							productType: item.productType,
+							productAvg: parseFloat(item.productAvg) || 0
+						}));
+
+						if (loadMore) {
+							// 加载更多,追加数据
+							this.selectedProducts = [...this.selectedProducts, ...productList];
+						} else {
+							// 初始加载或刷新
+							this.selectedProducts = productList;
+						}
+
+						// 更新分页信息
+						this.selectedTotal = res.data.total;
+						this.selectedHasMore = this.selectedParams.current < res.data.pages;
+					} else {
+						uni.showToast({
+							title: res.msg || '获取精选商品失败',
+							icon: 'none'
+						});
+					}
+				}).catch(err => {
+					console.error('获取精选商品失败:', err);
+					uni.showToast({
+						title: '获取精选商品失败',
+						icon: 'none'
+					});
+				}).finally(() => {
+					if (!loadMore) {
+						uni.hideLoading();
+					}
+				});
+			},
+
+			/**
+			 * 加载更多精选商品
+			 */
+			loadMoreSelectedProducts() {
+				if (!this.selectedHasMore || this.isLoading) {
+					return;
+				}
+
+				this.isLoading = true;
+				this.selectedParams.current++;
+				this.loadSelectedProducts(true);
+
+				setTimeout(() => {
+					this.isLoading = false;
+				}, 1000);
+			},
+
+			/**
+			 * 获取商品列表(入口方法)
+			 */
+			getProductList() {
+				// 并行加载代理商品和精选商品
+				this.loadAgentProducts();
+				this.loadSelectedProducts();
+			},
+
+			// 跳转到商品详情页
+			goDetail(id) {
+				uni.navigateTo({
+					url: '/packageShop/pages/detail/index?id=' + id
+				});
+			},
+
+			// 检查邀请
+			async checkInvitation() {
+				console.log('开始检查邀请, inviterId:', this.inviterId);
+
+				// 1. 优先检查URL参数中的邀请人ID(分享链接方式)
+				if (this.inviterId && !this.hasProcessedUrlInvitation) {
+					console.log('处理URL中的邀请人ID:', this.inviterId);
+					const urlInviterId = this.inviterId;
+					this.hasProcessedUrlInvitation = true; // 标记已处理,避免重复处理
+					await this.showInvitationDialog(urlInviterId, 'share');
+					return; // 处理了分享链接邀请就不再检查其他邀请
+				}
+
+				// 2. 检查本地存储中是否有待处理的邀请
+				const pendingInvitation = uni.getStorageSync('pendingInvitation');
+				console.log('本地存储的待处理邀请:', pendingInvitation);
+				if (pendingInvitation && pendingInvitation.inviterId) {
+					await this.showInvitationDialog(pendingInvitation.inviterId, 'local');
+					uni.removeStorageSync('pendingInvitation');
+					return;
+				}
+
+				// 3. 查询后端是否有待处理的邀请记录(账号邀请方式)
+				await this.checkPendingInvitationsFromServer();
+			},
+
+			// 查询后端待处理邀请记录
+			async checkPendingInvitationsFromServer() {
+				try {
+					// 获取当前用户信息
+					const currentUser = uni.getStorageSync('user');
+
+					console.log('当前用户信息:', currentUser ? currentUser.account : '未登录'); // 调试日志
+					if (!currentUser || !currentUser.account) {
+						console.log('用户未登录,跳过查询后端邀请');
+						return;
+					}
+
+					// 检查是否已有推荐人
+					if (currentUser.parentId) {
+						console.log('用户已有推荐人,跳过查询邀请', currentUser);
+						return;
+					}
+
+					console.log('查询后端待处理邀请记录...');
+					const res = await getPendingInvitations();
+
+					if (res.code === 200 && res.data && res.data.length > 0) {
+						console.log('发现待处理邀请:', res.data);
+						// 取第一个待处理的邀请(按时间倒序,最新的在前面)
+						const latestInvitation = res.data[0];
+						await this.showInvitationDialog(latestInvitation.inviterId, 'server', latestInvitation);
+					} else {
+						console.log('没有待处理的邀请记录');
+					}
+				} catch (error) {
+					console.error('查询待处理邀请失败:', error);
+				}
+			},
+
+			// 显示邀请弹框
+			async showInvitationDialog(inviterId, source = 'unknown', invitationData = null) {
+				try {
+					console.log(`显示邀请弹框 - 来源: ${source}, 邀请人ID: ${inviterId}`);
+
+					// 获取当前用户信息
+					const currentUser = uni.getStorageSync('user');
+					console.log('当前用户信息:', currentUser); // 调试日志
+
+					// 检查用户是否已有推荐人(仅在用户已登录时检查)
+					if (currentUser && currentUser.account && currentUser.parentId) {
+						console.log('用户已有推荐人,不显示邀请弹框');
+						return; // 已有推荐人,不显示邀请弹框
+					}
+
+					// 如果用户未登录,保存邀请信息到本地存储,但仍然显示邀请弹框
+					if (!currentUser || !currentUser.account) {
+						console.log('用户未登录,保存邀请信息到本地存储并显示邀请弹框');
+						uni.setStorageSync('pendingInvitation', {
+							inviterId
+						});
+					}
+
+					// 先设置邀请者ID,确保后续操作能够使用
+					this.inviterId = inviterId;
+					console.log('设置 this.inviterId =', this.inviterId);
+
+					// 如果是从服务器查询的邀请,直接使用已有的邀请人信息
+					if (source === 'server' && invitationData) {
+						this.inviterInfo = {
+							id: invitationData.inviterId,
+							account: invitationData.inviterAccount,
+							avatar: invitationData.inviterAvatar
+						};
+						this.showInvitationModal = true;
+						console.log('显示邀请弹框成功 (server)');
+						return;
+					}
+
+					// 其他情况需要查询邀请人信息
+					const res = await getMemberDetail(inviterId);
+
+					if (res.code === 200) {
+						this.inviterInfo = res.data;
+						this.showInvitationModal = true;
+						console.log('显示邀请弹框成功 (api success)');
+					} else {
+						console.log('获取邀请人信息失败:', res.msg);
+						// 即使获取邀请人信息失败,也显示一个默认的邀请弹框
+						this.inviterInfo = {
+							id: inviterId,
+							account: '好友',
+							avatar: '/static/images/avatar.png'
+						};
+						this.showInvitationModal = true;
+						console.log('使用默认信息显示邀请弹框 (api failed)');
+					}
+				} catch (error) {
+					console.error('获取邀请人信息失败:', error);
+					// 出现错误时也显示一个默认的邀请弹框
+					this.inviterInfo = {
+						id: inviterId,
+						account: '好友',
+						avatar: '/static/images/avatar.png'
+					};
+					this.inviterId = inviterId;
+					this.showInvitationModal = true;
+					console.log('使用默认信息显示邀请弹框 (error)');
+				}
+			},
+
+			// 接受邀请
+			async acceptInvitation() {
+				try {
+					console.log('开始接受邀请,当前 this.inviterId =', this.inviterId);
+
+					// 检查用户是否已登录
+					const currentUser = uni.getStorageSync('user');
+					console.log(currentUser, "当前用户信息")
+					if (!currentUser || !currentUser.account) {
+						// 用户未登录,跳转到登录页面
+						console.log('用户未登录,跳转到登录页面,保存 inviterId =', this.inviterId);
+						uni.showToast({
+							title: '请先登录',
+							icon: 'none'
+						});
+
+						// 保存邀请信息,登录后继续处理
+						uni.setStorageSync('pendingInvitation', {
+							inviterId: this.inviterId
+						});
+
+						// 关闭弹框
+						this.closeInvitationModal();
+
+						// 跳转到登录页面
+						setTimeout(() => {
+							uni.navigateTo({
+								url: '/packageUser/pages/login/index'
+							});
+						}, 500);
+						return;
+					}
+
+					// 用户已登录,直接处理邀请
+					console.log('用户已登录,准备调用 acceptInvitation API,inviterId =', this.inviterId);
+
+					if (!this.inviterId) {
+						console.error('邀请者ID为空,无法接受邀请');
+						uni.showToast({
+							title: '邀请信息错误',
+							icon: 'none'
+						});
+						return;
+					}
+
+					uni.showLoading({
+						title: '处理中...'
+					});
+					console.log('调用 acceptInvitation API,参数 inviterId =', this.inviterId);
+					const res = await acceptInvitation(this.inviterId);
+
+					if (res.code === 200) {
+						// 先隐藏加载遮罩
+						uni.hideLoading();
+
+						// 更新本地用户信息
+						const userInfo = uni.getStorageSync('user') || {};
+						userInfo.parentId = this.inviterId;
+						uni.setStorageSync('user', userInfo);
+
+						// 成功接受邀请后清空邀请ID
+						this.closeInvitationModal(true);
+
+						// 延迟显示成功提示,确保弹框关闭后再显示
+						setTimeout(() => {
+							this.showSuccessMessage();
+						}, 200);
+					} else {
+						uni.hideLoading();
+						setTimeout(() => {
+							uni.showToast({
+								title: res.msg || '操作失败',
+								icon: 'none',
+								duration: 2000,
+								mask: true
+							});
+						}, 100);
+					}
+				} catch (error) {
+					console.error('接受邀请失败:', error);
+					uni.hideLoading();
+					setTimeout(() => {
+						uni.showToast({
+							title: '操作失败',
+							icon: 'none',
+							duration: 2000,
+							mask: true
+						});
+					}, 100);
+				}
+			},
+
+			// 拒绝邀请
+			async rejectInvitation() {
+				try {
+					// 先隐藏邀请弹框,避免z-index冲突
+					const tempInviterId = this.inviterId;
+					const tempInviterInfo = this.inviterInfo;
+					this.showInvitationModal = false;
+
+					const res = await uni.showModal({
+						title: '确认拒绝',
+						content: '确定要拒绝这个邀请吗?'
+					});
+
+					if (res.confirm) {
+						uni.showLoading({
+							title: '处理中...'
+						});
+
+						const result = await rejectInvitation(tempInviterId);
+
+						if (result.code === 200) {
+							uni.showToast({
+								title: '已拒绝邀请',
+								icon: 'success'
+							});
+						} else {
+							uni.showToast({
+								title: result.msg || '操作失败',
+								icon: 'none'
+							});
+						}
+
+						// 拒绝成功后清除数据
+						this.closeInvitationModal(true);
+					} else {
+						// 用户取消拒绝,恢复邀请弹框
+						this.inviterId = tempInviterId;
+						this.inviterInfo = tempInviterInfo;
+						this.showInvitationModal = true;
+					}
+				} catch (error) {
+					console.error('拒绝邀请失败:', error);
+					uni.showToast({
+						title: '操作失败',
+						icon: 'none'
+					});
+				} finally {
+					uni.hideLoading();
+				}
+			},
+
+			// 显示成功消息(针对手机端优化)
+			showSuccessMessage() {
+				console.log('显示邀请接受成功消息');
+
+				// 获取系统信息判断环境
+				const systemInfo = uni.getSystemInfoSync();
+				console.log('当前环境:', systemInfo.platform);
+
+				// 优先尝试使用 showToast
+				try {
+					uni.showToast({
+						title: '邀请接受成功',
+						icon: 'success',
+						duration: 2500,
+						mask: true,
+						success: () => {
+							console.log('showToast 成功显示');
+
+						},
+						fail: (err) => {
+							console.error('showToast 失败:', err);
+							// 如果 showToast 失败,则使用 showModal 作为备选
+							this.showSuccessModal();
+						}
+					});
+				} catch (error) {
+					console.error('showToast 异常:', error);
+					// 异常情况下使用 showModal
+					this.showSuccessModal();
+				}
+			},
+
+			// 备选成功提示方法
+			showSuccessModal() {
+				console.log('使用 showModal 显示成功消息');
+				uni.showModal({
+					title: '成功',
+					content: '邀请接受成功!',
+					showCancel: false,
+					confirmText: '知道了',
+					success: (res) => {
+						console.log('showModal 成功显示');
+						try {
+							uni.vibrateShort({});
+						} catch (e) {
+							console.log('振动反馈不支持或失败');
+						}
+					}
+				});
+			},
+
+			// 关闭邀请弹框
+			closeInvitationModal(clearInviterId = false) {
+				this.showInvitationModal = false;
+				this.inviterInfo = {};
+				if (clearInviterId) {
+					this.inviterId = null;
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.status_bar {
+		height: var(--status-bar-height);
+		width: 100%;
+	}
+
+
+
+	.top_view {
+		height: var(--status-bar-height);
+		width: 100%;
+		position: fixed;
+		top: 0;
+		z-index: 999;
+	}
+
+	.container {
+		min-height: 100vh;
+		background: linear-gradient(135deg, #F5E6D3 0%, #E2D1C3 100%);
+		position: relative;
+	}
+
+	.container::before {
+		content: '';
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background:
+			radial-gradient(circle at 25% 25%, rgba(255, 215, 0, 0.15) 0%, transparent 25%),
+			radial-gradient(circle at 75% 75%, rgba(139, 69, 19, 0.1) 0%, transparent 25%),
+			radial-gradient(circle at 50% 50%, rgba(255, 215, 0, 0.08) 0%, transparent 30%);
+		pointer-events: none;
+		z-index: 0;
+		animation: mysticOrbs 12s ease-in-out infinite;
+	}
+
+	.container::after {
+		content: '';
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-image:
+			url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="mystic" width="40" height="40" patternUnits="userSpaceOnUse"><circle cx="20" cy="20" r="1" fill="%23A67C52" opacity="0.1"/><circle cx="10" cy="10" r="0.5" fill="%23FFD700" opacity="0.08"/><circle cx="30" cy="30" r="0.5" fill="%23FFD700" opacity="0.08"/></pattern></defs><rect width="100" height="100" fill="url(%23mystic)"/></svg>');
+		opacity: 0.2;
+		pointer-events: none;
+		z-index: 0;
+		animation: mysticPattern 18s linear infinite;
+	}
+
+	/* 顶部导航栏 */
+	.header {
+		background: linear-gradient(135deg, #9C6644 0%, #7E4F2D 100%);
+		padding: 0;
+		position: relative;
+		overflow: hidden;
+		border-bottom: 1rpx solid rgba(255, 215, 0, 0.1);
+	}
+
+	.fixed-header {
+		background: linear-gradient(135deg, #9C6644 0%, #7E4F2D 100%);
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		z-index: 100;
+		transform: translateY(-100%);
+		transition: transform 0.3s ease-in-out;
+		overflow: hidden;
+		box-shadow: 0 4rpx 20rpx rgba(156, 102, 68, 0.3);
+		border-bottom: 1rpx solid rgba(255, 215, 0, 0.1);
+	}
+
+	.fixed-header.show-fixed {
+		transform: translateY(0);
+	}
+
+	.fixed-header::before {
+		content: '';
+		position: absolute;
+		top: -50%;
+		left: -50%;
+		right: -50%;
+		bottom: -50%;
+		background:
+			radial-gradient(circle at 30% 50%, rgba(255, 215, 0, 0.25) 0%, transparent 60%),
+			radial-gradient(circle at 70% 50%, rgba(255, 215, 0, 0.2) 0%, transparent 60%),
+			radial-gradient(circle at 50% 30%, rgba(255, 215, 0, 0.15) 0%, transparent 50%);
+		opacity: 0.9;
+		animation: headerAura 15s ease-in-out infinite;
+		transform-origin: center;
+	}
+
+	.fixed-header::after {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="sacred" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M20 5 L35 20 L20 35 L5 20 Z" fill="none" stroke="rgba(255,215,0,0.15)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23sacred)"/></svg>');
+		opacity: 0.4;
+		animation: sacredPattern 15s linear infinite;
+		pointer-events: none;
+	}
+
+	.nav-content {
+		height: 90rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 0 24rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.left-section {
+		flex: 1;
+		display: flex;
+		align-items: center;
+	}
+
+	.logo-container {
+		display: flex;
+		align-items: center;
+		margin-left: 24rpx;
+		flex-shrink: 0;
+		white-space: nowrap;
+	}
+
+	.logo-image {
+		height: 60rpx;
+		width: 60rpx;
+		position: relative;
+		z-index: 1;
+		border-radius: 50%;
+		display: block;
+		border: 2rpx solid rgba(255, 215, 0, 0.3);
+	}
+
+	.logo-text {
+		font-size: 36rpx;
+		color: #FFD700;
+		font-weight: 600;
+		font-family: 'KaiTi', '楷体', serif;
+		margin-left: 12rpx;
+		letter-spacing: 2rpx;
+		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+		white-space: nowrap;
+	}
+
+	.right-section {
+		flex: 2;
+		display: flex;
+		align-items: center;
+		justify-content: flex-end;
+		padding-right: 200rpx;
+		/* 增加右侧padding,为胶囊按钮预留更多空间 */
+	}
+
+	.fixed-search-bar {
+		width: 300rpx;
+		/* 限制搜索框宽度 */
+		height: 64rpx;
+		display: flex;
+		align-items: center;
+		background: rgba(255, 248, 231, 0.95);
+		border: 2rpx solid rgba(139, 69, 19, 0.2);
+		border-radius: 32rpx;
+		padding: 0 20rpx;
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.15);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+		margin-right: 20rpx;
+		/* 添加右侧间距 */
+	}
+
+	.fixed-search-bar::before {
+		content: '';
+		position: absolute;
+		top: 2rpx;
+		left: 2rpx;
+		right: 2rpx;
+		bottom: 2rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 30rpx;
+		pointer-events: none;
+	}
+
+	.fixed-search-bar:active {
+		transform: scale(0.98);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.2);
+	}
+
+	.search-icon-wrapper {
+		width: 32rpx;
+		height: 32rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 12rpx;
+	}
+
+	.search-icon {
+		width: 28rpx;
+		height: 28rpx;
+		opacity: 0.6;
+	}
+
+	.search-input {
+		font-size: 26rpx;
+		color: #A67C52;
+		flex: 1;
+	}
+
+	.header::before {
+		content: '';
+		position: absolute;
+		top: -50%;
+		left: -50%;
+		right: -50%;
+		bottom: -50%;
+		background:
+			radial-gradient(circle at 30% 50%, rgba(255, 215, 0, 0.2) 0%, transparent 60%),
+			radial-gradient(circle at 70% 50%, rgba(255, 215, 0, 0.15) 0%, transparent 60%),
+			radial-gradient(circle at 50% 30%, rgba(255, 215, 0, 0.1) 0%, transparent 50%);
+		opacity: 0.8;
+		animation: headerAura 15s ease-in-out infinite;
+		transform-origin: center;
+	}
+
+	.header::after {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="sacred" width="30" height="30" patternUnits="userSpaceOnUse"><path d="M15 5 L25 15 L15 25 L5 15 Z" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23sacred)"/></svg>');
+		opacity: 0.3;
+		animation: sacredPattern 15s linear infinite;
+		pointer-events: none;
+	}
+
+	.header-content {
+		position: relative;
+		z-index: 1;
+		padding: 0rpx 30rpx 30rpx;
+	}
+
+	.logo-section {
+		margin-bottom: 20rpx;
+		width: 100%;
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+	}
+
+	.logo-section .logo-container {
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		margin-bottom: 12rpx;
+		margin-left: 24rpx;
+	}
+
+	.logo-section .logo-image {
+		height: 80rpx;
+		width: 80rpx;
+		border-radius: 50%;
+		display: block;
+		object-fit: cover;
+		border: 2rpx solid rgba(255, 215, 0, 0.3);
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+	}
+
+	.logo-section .logo-text {
+		font-size: 48rpx;
+		font-family: 'KaiTi', '楷体', serif;
+		white-space: nowrap;
+		color: #FFD700;
+		font-weight: 700;
+		margin-left: 16rpx;
+		letter-spacing: 3rpx;
+		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+	}
+
+	.sub-title {
+		font-size: 28rpx;
+		color: rgba(255, 215, 0, 0.8);
+		margin-top: 12rpx;
+		font-weight: 300;
+		letter-spacing: 1rpx;
+		margin-left: 24rpx;
+	}
+
+	.search-bar {
+		display: flex;
+		align-items: center;
+		background: rgba(255, 248, 231, 0.95);
+		border: 2rpx solid rgba(139, 69, 19, 0.2);
+		border-radius: 50rpx;
+		padding: 20rpx 30rpx;
+		box-shadow: 0 8rpx 32rpx rgba(139, 69, 19, 0.15);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+	}
+
+	.search-bar::before {
+		content: '';
+		position: absolute;
+		top: 4rpx;
+		left: 4rpx;
+		right: 4rpx;
+		bottom: 4rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 46rpx;
+		pointer-events: none;
+	}
+
+	.search-bar:active {
+		transform: scale(0.98);
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.2);
+	}
+
+	.search-icon-wrapper {
+		width: 48rpx;
+		height: 48rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 16rpx;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+	}
+
+	.search-icon {
+		width: 28rpx;
+		height: 28rpx;
+		filter: brightness(0) invert(1);
+	}
+
+	.search-input {
+		flex: 1;
+		font-size: 30rpx;
+		color: #8B4513;
+		font-weight: 400;
+	}
+
+	/* 轮播图 */
+	.swiper-section {
+		margin: 20rpx 20rpx 0;
+		position: relative;
+	}
+
+	.swiper-container {
+		border-radius: 20rpx;
+		overflow: hidden;
+		box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.12);
+		position: relative;
+	}
+
+	.swiper-container::before {
+		content: '';
+		position: absolute;
+		top: 4rpx;
+		left: 4rpx;
+		right: 4rpx;
+		bottom: 4rpx;
+		border: 1rpx solid rgba(255, 215, 0, 0.2);
+		border-radius: 16rpx;
+		pointer-events: none;
+		z-index: 2;
+	}
+
+	.swiper {
+		width: 100%;
+		height: 280rpx;
+	}
+
+	.swiper-image {
+		width: 100%;
+		height: 100%;
+	}
+
+	/* 分类导航 */
+	.category-section {
+		margin: 20rpx 20rpx;
+		background: rgba(255, 248, 231, 0.9);
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 20rpx;
+		padding: 24rpx 20rpx;
+		box-shadow: 0 8rpx 32rpx rgba(139, 69, 19, 0.08);
+		backdrop-filter: blur(10rpx);
+		position: relative;
+	}
+
+	.category-section::before {
+		content: '';
+		position: absolute;
+		top: 10rpx;
+		left: 10rpx;
+		right: 10rpx;
+		bottom: 10rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 20rpx;
+		pointer-events: none;
+	}
+
+	.category-header {
+		text-align: center;
+		margin-bottom: 30rpx;
+		position: relative;
+	}
+
+	.category-title {
+		font-size: 32rpx;
+		font-weight: 700;
+		color: #8B4513;
+		position: relative;
+		display: inline-block;
+		padding: 0 40rpx;
+	}
+
+	.category-title::before,
+	.category-title::after {
+		content: '❈';
+		color: #8B4513;
+		opacity: 0.6;
+		font-size: 24rpx;
+		position: absolute;
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.category-title::before {
+		left: 0;
+	}
+
+	.category-title::after {
+		right: 0;
+	}
+
+	.category-decoration {
+		width: 100rpx;
+		height: 2rpx;
+		background: linear-gradient(90deg, transparent, #8B4513, transparent);
+		margin: 16rpx auto 0;
+	}
+
+	.category-grid {
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 20rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.category-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		transition: all 0.3s ease;
+	}
+
+	.category-item:active {
+		transform: scale(0.95);
+	}
+
+	.category-icon-wrapper {
+		width: 100rpx;
+		height: 100rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border: 2rpx solid #FFD700;
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 12rpx;
+		box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+	}
+
+	.category-item:active .category-icon-wrapper {
+		transform: scale(0.9);
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.category-icon {
+		width: 100rpx;
+		height: 100rpx;
+		border-radius: 50%;
+	}
+
+	.category-name {
+		font-size: 24rpx;
+		color: #8B4513;
+		font-weight: 500;
+		text-align: center;
+	}
+
+	/* 爆款产品 */
+	.hot-section {
+		margin: 20rpx 20rpx;
+		background: linear-gradient(135deg, rgba(255, 248, 231, 0.95) 0%, rgba(255, 235, 205, 0.95) 100%);
+		border: 2rpx solid rgba(139, 69, 19, 0.15);
+		border-radius: 24rpx;
+		padding: 30rpx 20rpx;
+		box-shadow: 0 12rpx 40rpx rgba(139, 69, 19, 0.12);
+		backdrop-filter: blur(10rpx);
+		position: relative;
+		overflow: hidden;
+	}
+
+	.hot-section::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="hot-pattern" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="%23A67C52" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23hot-pattern)"/></svg>');
+		opacity: 0.3;
+		pointer-events: none;
+	}
+
+	.hot-header {
+		text-align: center;
+		margin-bottom: 30rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.hot-title {
+		font-size: 36rpx;
+		font-weight: 700;
+		color: #8B4513;
+		text-shadow: 0 2rpx 4rpx rgba(139, 69, 19, 0.2);
+		position: relative;
+		display: inline-block;
+		padding: 0 40rpx;
+		animation: hotTitleGlow 3s ease-in-out infinite;
+	}
+
+	.hot-title::before,
+	.hot-title::after {
+		content: '❈';
+		color: #8B4513;
+		opacity: 0.6;
+		font-size: 24rpx;
+		position: absolute;
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.hot-title::before {
+		left: 0;
+	}
+
+	.hot-title::after {
+		right: 0;
+	}
+
+	.hot-decoration {
+		width: 100rpx;
+		height: 2rpx;
+		background: linear-gradient(90deg, transparent, #8B4513, transparent);
+		margin: 16rpx auto 0;
+	}
+
+	.hot-grid {
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 20rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.hot-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		transition: all 0.3s ease;
+	}
+
+	.hot-item:active {
+		transform: scale(0.95);
+	}
+
+	.hot-icon-wrapper {
+		width: 120rpx;
+		height: 120rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border: 3rpx solid #FFD700;
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 12rpx;
+		box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+		position: relative;
+		overflow: hidden;
+		animation: mysticPulse 3s ease-in-out infinite;
+	}
+
+	.hot-icon-wrapper::before {
+		content: '';
+		position: absolute;
+		top: -50%;
+		left: -50%;
+		width: 200%;
+		height: 200%;
+		background: conic-gradient(from 0deg,
+				transparent 0deg,
+				rgba(255, 215, 0, 0.1) 45deg,
+				rgba(255, 215, 0, 0.3) 90deg,
+				rgba(255, 215, 0, 0.1) 135deg,
+				transparent 180deg,
+				rgba(255, 215, 0, 0.05) 225deg,
+				rgba(255, 215, 0, 0.2) 270deg,
+				rgba(255, 215, 0, 0.05) 315deg,
+				transparent 360deg);
+		transform: rotate(0deg);
+		transition: all 0.3s ease;
+		animation: mysticRotate 6s ease-in-out infinite;
+	}
+
+	.hot-item:active .hot-icon-wrapper {
+		transform: scale(0.9);
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.4);
+		animation: hotClick 0.3s ease-out;
+	}
+
+	.hot-item:active .hot-icon-wrapper::before {
+		transform: rotate(45deg) translateX(100%);
+	}
+
+	.hot-glow {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		width: 140rpx;
+		height: 140rpx;
+		background: radial-gradient(circle,
+				rgba(255, 215, 0, 0.4) 0%,
+				rgba(255, 215, 0, 0.2) 30%,
+				rgba(255, 215, 0, 0.1) 60%,
+				transparent 80%);
+		border-radius: 50%;
+		transform: translate(-50%, -50%);
+		animation: hotGlow 3s ease-in-out infinite alternate;
+		pointer-events: none;
+		z-index: 0;
+		filter: blur(2rpx);
+	}
+
+	@keyframes mysticPulse {
+		0% {
+			box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3);
+			transform: scale(1);
+		}
+
+		25% {
+			box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3), 0 0 20rpx rgba(255, 215, 0, 0.3);
+			transform: scale(1.02);
+		}
+
+		50% {
+			box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3), 0 0 30rpx rgba(255, 215, 0, 0.5);
+			transform: scale(1.05);
+		}
+
+		75% {
+			box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3), 0 0 25rpx rgba(255, 215, 0, 0.4);
+			transform: scale(1.03);
+		}
+
+		100% {
+			box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.3);
+			transform: scale(1);
+		}
+	}
+
+	@keyframes mysticRotate {
+		0% {
+			transform: rotate(0deg);
+		}
+
+		25% {
+			transform: rotate(90deg);
+		}
+
+		50% {
+			transform: rotate(180deg);
+		}
+
+		75% {
+			transform: rotate(270deg);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+
+	@keyframes hotGlow {
+		0% {
+			opacity: 0.4;
+			transform: translate(-50%, -50%) scale(0.9);
+		}
+
+		50% {
+			opacity: 0.7;
+			transform: translate(-50%, -50%) scale(1.1);
+		}
+
+		100% {
+			opacity: 0.5;
+			transform: translate(-50%, -50%) scale(1.0);
+		}
+	}
+
+	@keyframes hotClick {
+		0% {
+			transform: scale(1);
+		}
+
+		50% {
+			transform: scale(0.85);
+		}
+
+		100% {
+			transform: scale(0.9);
+		}
+	}
+
+	@keyframes hotTitleGlow {
+
+		0%,
+		100% {
+			text-shadow: 0 2rpx 4rpx rgba(139, 69, 19, 0.2);
+		}
+
+		50% {
+			text-shadow: 0 2rpx 4rpx rgba(139, 69, 19, 0.2), 0 0 10rpx rgba(255, 215, 0, 0.6);
+		}
+	}
+
+	@keyframes mysticOrbs {
+
+		0%,
+		100% {
+			transform: scale(1) rotate(0deg);
+		}
+
+		25% {
+			transform: scale(1.1) rotate(90deg);
+		}
+
+		50% {
+			transform: scale(0.9) rotate(180deg);
+		}
+
+		75% {
+			transform: scale(1.05) rotate(270deg);
+		}
+	}
+
+	@keyframes mysticPattern {
+		0% {
+			transform: translateX(0px) translateY(0px);
+		}
+
+		100% {
+			transform: translateX(-40px) translateY(-40px);
+		}
+	}
+
+	@keyframes headerAura {
+		0% {
+			transform: scale(1) rotate(0deg);
+			opacity: 0.8;
+		}
+
+		25% {
+			transform: scale(1.1) rotate(5deg);
+			opacity: 0.9;
+		}
+
+		50% {
+			transform: scale(1.15) rotate(0deg);
+			opacity: 1;
+		}
+
+		75% {
+			transform: scale(1.1) rotate(-5deg);
+			opacity: 0.9;
+		}
+
+		100% {
+			transform: scale(1) rotate(0deg);
+			opacity: 0.8;
+		}
+	}
+
+	@keyframes sacredPattern {
+		0% {
+			transform: translateX(0px) translateY(0px) rotate(0deg);
+		}
+
+		100% {
+			transform: translateX(-30px) translateY(-30px) rotate(360deg);
+		}
+	}
+
+	.hot-icon {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+		z-index: 1;
+		position: relative;
+	}
+
+	.hot-badge {
+		position: absolute;
+		top: 5rpx;
+		right: 8rpx;
+		background: linear-gradient(135deg, #FF6B35 0%, #FF9A56 100%);
+		color: #fff;
+		font-size: 18rpx;
+		padding: 4rpx 8rpx;
+		border-radius: 10rpx;
+		font-weight: 600;
+		z-index: 2;
+		box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.4);
+		border: 1rpx solid rgba(255, 255, 255, 0.3);
+	}
+
+	.hot-name {
+		font-size: 24rpx;
+		color: #8B4513;
+		font-weight: 500;
+		text-align: center;
+		line-height: 1.3;
+	}
+
+	/* 商品区域通用样式 */
+	.product-section {
+		margin: 0 20rpx 40rpx;
+	}
+
+	.section-header {
+		text-align: center;
+		margin-bottom: 30rpx;
+		position: relative;
+		padding: 30rpx 0;
+	}
+
+	.section-header::before,
+	.section-header::after {
+		content: '';
+		position: absolute;
+		height: 2rpx;
+		width: 60rpx;
+		background: linear-gradient(90deg, transparent, #8B4513, transparent);
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.section-header::before {
+		left: 20%;
+	}
+
+	.section-header::after {
+		right: 20%;
+	}
+
+	.section-title {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 12rpx;
+	}
+
+	.title-text {
+		font-size: 36rpx;
+		font-weight: 700;
+		color: #8B4513;
+		margin: 0 20rpx;
+		position: relative;
+		display: inline-block;
+	}
+
+	.title-decoration {
+		display: flex;
+		align-items: center;
+		gap: 10rpx;
+	}
+
+	.decoration-line {
+		width: 40rpx;
+		height: 2rpx;
+		background: linear-gradient(90deg, #8B4513, #654321);
+	}
+
+	.decoration-symbol {
+		width: 30rpx;
+		height: 30rpx;
+		opacity: 0.8;
+	}
+
+	.section-subtitle {
+		font-size: 26rpx;
+		color: #A67C52;
+		font-weight: 300;
+	}
+
+	/* 代理商品列表 */
+	.agent-section {
+		background: rgba(255, 248, 231, 0.5);
+		border-radius: 24rpx;
+		padding: 20rpx;
+		margin: 0 20rpx 40rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+	}
+
+	.product-list {
+		display: grid;
+		grid-template-columns: repeat(2, 1fr);
+		gap: 20rpx;
+	}
+
+	.product-item {
+		background: rgba(255, 248, 231, 0.95);
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 20rpx;
+		overflow: hidden;
+		box-shadow: 0 8rpx 32rpx rgba(139, 69, 19, 0.08);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+	}
+
+	.product-item::before {
+		content: '';
+		position: absolute;
+		top: 10rpx;
+		left: 10rpx;
+		right: 10rpx;
+		bottom: 10rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 16rpx;
+		pointer-events: none;
+		z-index: 1;
+	}
+
+	.product-item:active {
+		transform: translateY(-4rpx);
+		box-shadow: 0 12rpx 40rpx rgba(139, 69, 19, 0.12);
+	}
+
+	.product-image-container {
+		position: relative;
+		width: 100%;
+		height: 280rpx;
+		overflow: hidden;
+	}
+
+	.product-image {
+		width: 100%;
+		height: 100%;
+		transition: transform 0.3s ease;
+	}
+
+	.product-item:active .product-image {
+		transform: scale(1.05);
+	}
+
+	.agent-badge {
+		position: absolute;
+		top: 16rpx;
+		right: 16rpx;
+		background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
+		color: #8B4513;
+		font-size: 22rpx;
+		padding: 6rpx 12rpx;
+		border-radius: 12rpx;
+		font-weight: 600;
+		border: 1rpx solid rgba(255, 255, 255, 0.3);
+		z-index: 2;
+	}
+
+	.product-content {
+		padding: 20rpx;
+		position: relative;
+		z-index: 2;
+	}
+
+	.product-name {
+		font-size: 28rpx;
+		color: #8B4513;
+		font-weight: 600;
+		line-height: 1.4;
+		margin-bottom: 8rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+	}
+
+	.product-desc {
+		font-size: 24rpx;
+		color: #A67C52;
+		margin-bottom: 12rpx;
+	}
+
+	.product-meta {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 16rpx;
+	}
+
+	.update-time {
+		font-size: 22rpx;
+		color: #999;
+	}
+
+	.sold-count {
+		font-size: 22rpx;
+		color: #999;
+	}
+
+	.product-footer {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.price-section {
+		display: flex;
+		align-items: baseline;
+	}
+
+	.price-symbol {
+		font-size: 24rpx;
+		color: #8B4513;
+		font-weight: 600;
+	}
+
+	.price-value {
+		font-size: 32rpx;
+		color: #8B4513;
+		font-weight: 700;
+	}
+
+	.buy-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		padding: 12rpx 20rpx;
+		border-radius: 20rpx;
+		border: 1rpx solid #FFD700;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+	}
+
+	.agent-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border: 1rpx solid #FFD700;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+	}
+
+	.buy-btn:active {
+		transform: scale(0.95);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.agent-btn:active {
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.buy-text {
+		font-size: 24rpx;
+		color: #FFD700;
+		font-weight: 500;
+	}
+
+	/* 精选商品网格 */
+	.selected-section {
+		background: rgba(255, 248, 231, 0.3);
+		border-radius: 24rpx;
+		padding: 20rpx;
+		margin: 0 20rpx 40rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+	}
+
+	.selected-product-grid {
+		display: grid;
+		grid-template-columns: repeat(2, 1fr);
+		gap: 20rpx;
+	}
+
+	.selected-product-card {
+		background: rgba(255, 248, 231, 0.95);
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 20rpx;
+		overflow: hidden;
+		box-shadow: 0 8rpx 32rpx rgba(139, 69, 19, 0.08);
+		backdrop-filter: blur(10rpx);
+		transition: all 0.3s ease;
+		position: relative;
+	}
+
+	.selected-product-card::before {
+		content: '';
+		position: absolute;
+		top: 10rpx;
+		left: 10rpx;
+		right: 10rpx;
+		bottom: 10rpx;
+		border: 1rpx solid rgba(139, 69, 19, 0.1);
+		border-radius: 16rpx;
+		pointer-events: none;
+		z-index: 1;
+	}
+
+	.selected-product-card:active {
+		transform: translateY(-4rpx);
+		box-shadow: 0 12rpx 40rpx rgba(139, 69, 19, 0.12);
+	}
+
+	.selected-product-image-wrapper {
+		position: relative;
+		width: 100%;
+		height: 280rpx;
+		overflow: hidden;
+	}
+
+	.selected-product-image {
+		width: 100%;
+		height: 100%;
+		transition: transform 0.3s ease;
+	}
+
+	.selected-product-card:active .selected-product-image {
+		transform: scale(1.05);
+	}
+
+	.selected-product-badge {
+		position: absolute;
+		top: 16rpx;
+		right: 16rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		border: 1rpx solid #FFD700;
+		color: #FFD700;
+		font-size: 22rpx;
+		padding: 6rpx 12rpx;
+		border-radius: 12rpx;
+		font-weight: 500;
+		z-index: 2;
+	}
+
+	.selected-product-info {
+		padding: 20rpx;
+		position: relative;
+		z-index: 2;
+	}
+
+	.selected-product-name {
+		font-size: 28rpx;
+		color: #8B4513;
+		font-weight: 600;
+		line-height: 1.4;
+		margin-bottom: 8rpx;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+	}
+
+	.selected-product-desc {
+		font-size: 24rpx;
+		color: #A67C52;
+		margin-bottom: 12rpx;
+	}
+
+	.selected-product-meta {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 16rpx;
+	}
+
+	.rating {
+		display: flex;
+		align-items: center;
+		gap: 4rpx;
+	}
+
+	.rating-star {
+		font-size: 24rpx;
+		color: #FFD700;
+	}
+
+	.rating-value {
+		font-size: 22rpx;
+		color: #666;
+		font-weight: 500;
+	}
+
+	.selected-sold-count {
+		font-size: 22rpx;
+		color: #999;
+	}
+
+	.selected-product-footer {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.selected-price-section {
+		display: flex;
+		align-items: baseline;
+	}
+
+	.selected-price-symbol {
+		font-size: 24rpx;
+		color: #8B4513;
+		font-weight: 600;
+	}
+
+	.selected-price-value {
+		font-size: 32rpx;
+		color: #8B4513;
+		font-weight: 700;
+	}
+
+	.selected-buy-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		padding: 12rpx 20rpx;
+		border-radius: 20rpx;
+		border: 1rpx solid #FFD700;
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+		transition: all 0.3s ease;
+	}
+
+	.selected-buy-btn:active {
+		transform: scale(0.95);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	.selected-buy-text {
+		font-size: 24rpx;
+		color: #FFD700;
+		font-weight: 500;
+	}
+
+	/* 底部装饰 */
+	.bottom-decoration {
+		height: 60rpx;
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		margin: 0 20rpx;
+		border-radius: 20rpx 20rpx 0 0;
+		opacity: 0.1;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.bottom-decoration::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="endless-knot-bottom" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M10 0L20 10L10 20L0 10z" fill="none" stroke="rgba(255,215,0,0.1)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23endless-knot-bottom)"/></svg>');
+		opacity: 0.2;
+	}
+
+	/* 邀请弹框样式 */
+	.invitation-modal {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.6);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		z-index: 99999;
+		backdrop-filter: blur(5rpx);
+	}
+
+	.modal-content {
+		width: 600rpx;
+		max-width: 90vw;
+		background: linear-gradient(135deg, rgba(255, 248, 231, 0.98) 0%, rgba(255, 235, 205, 0.98) 100%);
+		border-radius: 24rpx;
+		overflow: hidden;
+		box-shadow: 0 20rpx 60rpx rgba(139, 69, 19, 0.3);
+		border: 2rpx solid rgba(255, 215, 0, 0.3);
+		position: relative;
+		animation: modalSlideIn 0.3s ease-out;
+	}
+
+	.modal-content::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="modal-pattern" width="30" height="30" patternUnits="userSpaceOnUse"><circle cx="15" cy="15" r="1" fill="%23A67C52" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23modal-pattern)"/></svg>');
+		opacity: 0.3;
+		pointer-events: none;
+	}
+
+	.modal-header {
+		background: linear-gradient(135deg, #9C6644 0%, #7E4F2D 100%);
+		padding: 30rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		position: relative;
+		z-index: 1;
+	}
+
+	.modal-title {
+		font-size: 32rpx;
+		font-weight: 700;
+		color: #FFD700;
+		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+	}
+
+	.close-btn {
+		width: 48rpx;
+		height: 48rpx;
+		background: rgba(255, 255, 255, 0.2);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 24rpx;
+		color: #FFD700;
+		font-weight: 600;
+		transition: all 0.3s ease;
+	}
+
+	.close-btn:active {
+		background: rgba(255, 255, 255, 0.3);
+		transform: scale(0.9);
+	}
+
+	.modal-body {
+		padding: 40rpx 30rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.inviter-info {
+		display: flex;
+		align-items: center;
+		margin-bottom: 30rpx;
+		background: rgba(255, 255, 255, 0.8);
+		border-radius: 20rpx;
+		padding: 24rpx;
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.1);
+	}
+
+	.inviter-avatar {
+		width: 100rpx;
+		height: 100rpx;
+		border-radius: 50%;
+		margin-right: 20rpx;
+		border: 3rpx solid #FFD700;
+		box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
+	}
+
+	.inviter-details {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.inviter-name {
+		font-size: 32rpx;
+		font-weight: 700;
+		color: #8B4513;
+		margin-bottom: 8rpx;
+	}
+
+	.inviter-desc {
+		font-size: 26rpx;
+		color: #A67C52;
+	}
+
+	.invitation-desc {
+		background: rgba(255, 255, 255, 0.6);
+		border-radius: 16rpx;
+		padding: 24rpx;
+		border-left: 4rpx solid #8B4513;
+	}
+
+	.desc-text {
+		font-size: 26rpx;
+		color: #666;
+		line-height: 1.6;
+	}
+
+	.modal-footer {
+		padding: 0 30rpx 30rpx;
+		display: flex;
+		gap: 20rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.reject-btn,
+	.accept-btn {
+		flex: 1;
+		height: 80rpx;
+		border-radius: 40rpx;
+		font-size: 28rpx;
+		font-weight: 600;
+		border: none;
+		transition: all 0.3s ease;
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.2);
+	}
+
+	.reject-btn {
+		background: rgba(255, 255, 255, 0.9);
+		color: #999;
+		border: 2rpx solid rgba(153, 153, 153, 0.3);
+	}
+
+	.reject-btn:active {
+		background: rgba(255, 255, 255, 0.7);
+		transform: scale(0.98);
+	}
+
+	.accept-btn {
+		background: linear-gradient(135deg, #8B4513 0%, #654321 100%);
+		color: #FFD700;
+		border: 2rpx solid #FFD700;
+		box-shadow: 0 4rpx 16rpx rgba(139, 69, 19, 0.3), 0 0 20rpx rgba(255, 215, 0, 0.2);
+	}
+
+	.accept-btn:active {
+		transform: scale(0.98);
+		box-shadow: 0 2rpx 8rpx rgba(139, 69, 19, 0.4);
+	}
+
+	@keyframes modalSlideIn {
+		0% {
+			opacity: 0;
+			transform: scale(0.8) translateY(-50rpx);
+		}
+
+		100% {
+			opacity: 1;
+			transform: scale(1) translateY(0);
+		}
+	}
+
+	/* 响应式设计 */
+	@media (max-width: 750rpx) {
+		.selected-product-grid {
+			grid-template-columns: 1fr;
+		}
+
+		.category-grid {
+			grid-template-columns: repeat(3, 1fr);
+		}
+
+		.category-item {
+			width: 120rpx;
+		}
+
+		.modal-content {
+			width: 90vw;
+		}
+
+		.modal-footer {
+			flex-direction: column;
+		}
+
+		.reject-btn,
+		.accept-btn {
+			width: 100%;
+		}
+	}
+
+	/* 加载更多按钮样式 */
+	.load-more-section {
+		display: flex;
+		justify-content: center;
+		padding: 40rpx 20rpx;
+	}
+
+	.load-more-btn {
+		background: linear-gradient(135deg, #8B4513, #A0522D);
+		color: white;
+		padding: 24rpx 60rpx;
+		border-radius: 50rpx;
+		font-size: 28rpx;
+		font-weight: 600;
+		box-shadow: 0 8rpx 24rpx rgba(139, 69, 19, 0.2);
+		transition: all 0.3s ease;
+		text-align: center;
+		min-width: 200rpx;
+	}
+
+	.load-more-btn.loading {
+		background: linear-gradient(135deg, #999, #bbb);
+		color: #666;
+	}
+
+	.load-more-btn:active {
+		transform: translateY(2rpx);
+		box-shadow: 0 4rpx 12rpx rgba(139, 69, 19, 0.3);
+	}
+
+	/* 没有更多数据提示样式 */
+	.no-more-tip {
+		display: flex;
+		justify-content: center;
+		padding: 30rpx 20rpx;
+		color: #999;
+		font-size: 26rpx;
+	}
+</style>

+ 576 - 0
pages/order/after-progress.vue

@@ -0,0 +1,576 @@
+<template>
+	<view>
+		<view class="mar-t-10">
+
+		</view>
+		<CustomToast ref="customToast" />
+		<view class="region mar-t-20" v-if="order.isAfter!=3">
+			<view>
+				<uni-steps active-color="#e29c6d" :options="list1" :active="active" :key="activeKey"
+					v-if="orderAfter.type==1" />
+				<uni-steps active-color="#e29c6d" :options="list2" :active="active" :key="activeKey"
+					v-if="orderAfter.type==2" />
+			</view>
+		</view>
+
+		<view class="region pad-24">
+			<view v-if="orderAfter.status==1">
+				<view class="font38 font-bold ">
+					请寄回商品
+				</view>
+				<view class="mar-tb-20 font28">
+					商家已同意退款,请您在
+					<text class="font-red font28 pad-lr-10">
+						{{remainingTime}}
+					</text>
+					内寄回商品
+				</view>
+				<view class="flex  flex-sp-between mar-t-20 ">
+					<view class="nowrap mar-r-20 ">
+						商家地址信息
+					</view>
+					<view>
+						<view class="flex-end">
+							<text>
+								{{merchantAddress.name}}
+							</text>
+							<text>
+								{{merchantAddress.mobile}}
+							</text>
+						</view>
+						<view>
+							{{merchantAddress.address}}
+						</view>
+					</view>
+				</view>
+				<view class="flex-end mar-t-20">
+					<text class="font-blue " @tap="copyContent">复制</text>
+				</view>
+				<view class="gray-line mar-tb-8"></view>
+			</view>
+			<view class="font38 font-bold " v-if="orderAfter.status==0">
+				请等待商家审核
+			</view>
+			<view class="font38 font-bold " v-if="orderAfter.status==1">
+				请寄回商品
+			</view>
+			<view class="font38 font-bold " v-if="orderAfter.status==2">
+				等待商家退款
+			</view>
+			<view v-if="orderAfter.status==3">
+				<view class="flex-items-plus">
+					<uni-icons color="#63b43d" type="checkbox-filled" size="30"></uni-icons>
+					<view class="font38 font-bold ">
+						退款完成
+					</view>
+				</view>
+				<view class="text-align mar-t-10">
+					钱款{{orderAfter.afterPrice}}元已退回到你**账户
+				</view>
+			</view>
+
+			<view @tap="checkAfterSaleStatus" class="flex-items-between">
+				<view>
+
+					<view v-if="orderAfter.status==4">
+						<view class="font-red">
+							售后已关闭
+						</view>
+						<view class="font38 font-bold ">
+							商家审核不通过
+						</view>
+						<view class="font28">
+							原因:{{orderAfter.approveRemark}}
+						</view>
+					</view>
+					<view v-if="orderAfter.status==5">
+						<view class="font-red">
+							售后已关闭
+						</view>
+						<view class="font38 font-bold ">
+							商家不同意退款
+						</view>
+						<view class="font28">
+							原因:{{orderAfter.approveRemark}}
+						</view>
+					</view>
+					<view v-if="orderAfter.status==6">
+						<view class="font-red">
+							售后已关闭
+						</view>
+						<view class="font38 font-bold ">
+							您已取消售后
+						</view>
+
+					</view>
+				</view>
+
+			</view>
+
+			<view class="flex-items-between mar-t-20">
+				<view>
+					<view class="font28" v-if="order.isAfter==1&&(orderAfter.status==1||orderAfter.status==2)">
+						我已寄出
+					</view>
+					<view class="font-gray" v-if="order.isAfter==1&&(orderAfter.status==1)">
+						填写物流单号
+					</view>
+				</view>
+				<view>
+					<text class="copy-btn " @tap="logistics"
+						v-if="orderAfter.status==2||orderAfter.status==3">查询物流</text>
+					<text class="copy-btn " @tap="showExpressNumberPopup" v-if="orderAfter.status==1">填写单号</text>
+				</view>
+			</view>
+		</view>
+
+		<view class="region2">
+			<view class="font32 pad-16 ">
+				退货信息
+			</view>
+			<view class="flex-items">
+				<view>
+					<image class=" mar-l-20 productImg" :src="orderAfter.productImg" mode="cover"></image>
+				</view>
+				<view class="mar-l-20 mar-r-20">
+					<view class="font28 pad-b-10 ">
+						{{orderAfter.productName}}
+					</view>
+					<view class="pad-tb-16 font-gray">
+						<text class="font28">
+							规格: {{orderAfter.skuName}}
+						</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="flex-items-between pad-24">
+				<view>
+					退货件数
+				</view>
+				<view>
+					{{orderAfter.count}}件
+				</view>
+			</view>
+			<view class="flex-items-between pad-24">
+				<view>
+					退款金额
+				</view>
+				<view>
+					{{orderAfter.afterPrice}}
+				</view>
+			</view>
+			<view class="flex-items-between pad-24">
+				<view>
+					退货类型
+				</view>
+				<view>
+					{{orderAfter.type==1?'退货退款':'仅退款'}}
+				</view>
+			</view>
+			<view class="flex-items-between pad-24">
+				<view>
+					退货原因
+				</view>
+				<view>
+					{{reasonRange[orderAfter.reason].text}}
+				</view>
+			</view>
+			<view class="flex-items-between pad-24">
+				<view>
+					申请时间
+				</view>
+				<view>
+					{{orderAfter.createTime}}
+				</view>
+			</view>
+			<view class="flex-items-between pad-24">
+				<view>
+					订单编号
+				</view>
+				<view>
+					{{orderAfter.orderCode}}
+				</view>
+			</view>
+			<view class="pad-24">
+				<view>
+					凭证
+				</view>
+				<view class="image-item mar-tb-16">
+					<view v-for="(image, imageIndex) in imagesList" :key="imageIndex" class="mar-r-20 "
+						v-if="imagesList.length>0">
+						<image @tap="detailPreviewImage(imageIndex)" :src="image" class="images">
+						</image>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<uni-popup ref="dialog" type="dialog">
+			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确认" title="提示" content="确认要取消退货?"
+				@confirm="cancel()"></uni-popup-dialog>
+		</uni-popup>
+		<!-- 底部弹窗 -->
+		<uni-popup ref="expressNumberPopup" borderRadius="20rpx 20rpx 0 0 " background-color="#fff">
+			<view class="popup-content">
+				<view class="popup-title">填写物流单号</view>
+				<input v-model="orderAfter.expressCode" focus class="popup-input" placeholder="请输入物流单号" />
+				<view class="popup-buttons">
+					<button class="popup-cancel" @click="hideExpressNumberPopup">取消</button>
+					<button class="popup-confirm" @click="submitExpressNumber">确定</button>
+				</view>
+			</view>
+		</uni-popup>
+		<view class="navigation">
+			<view class="flex-items-between">
+				<view class="mar-l-20" @click="$Router.pushTab({name: 'index'})">
+					<u-icon name="chat" color="#F95B5B" size="22"></u-icon>
+					<view>客服</view>
+				</view>
+
+				<view class="flex-items">
+					<button class="grayBut mar-r-20 " @click="onCancelAfter" v-if="order.isAfter==1">取消售后</button>
+					<button class="grayBut mar-r-20 " @click="submitAfterSale " v-if="order.isAfter==3">再次申请售后</button>
+					<button class="grayBut mar-r-20" @click="afterRecord" v-if="historyDetail!=1">售后记录</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		merchantAddressDetail,
+		orderDetail,
+		orderAfterDetail,
+		orderAfterDetailById,
+		updateExpressCode,
+		cancelAfter
+	} from '../../config/api.js';
+	import CustomToast from '../../components/CustomToast.vue';
+
+	export default {
+		components: {
+			CustomToast,
+		},
+		data() {
+			return {
+				activeKey: 0,
+				remainingTime: "", // 剩余时间
+				merchantAddress: {},
+				active: 0,
+				imagesList: [],
+				order: {},
+				orderAfter: {
+					reason: 0,
+					expressCode: ""
+				},
+				list1: [{
+					title: '商家审核'
+				}, {
+					title: '寄回商品'
+				}, {
+					title: '商家退款'
+				}, {
+					title: '退款完成'
+				}],
+				list2: [{
+					title: '商家审核'
+				}, {
+					title: '退款完成'
+				}],
+				orderId: "",
+				historyDetail: "",
+				reasonRange: [{
+						value: 1,
+						text: "不喜欢,效果不好"
+					},
+					{
+						value: 2,
+						text: "不想要了"
+					},
+					{
+						value: 3,
+						text: "材质与商品描述不符"
+					},
+					{
+						value: 4,
+						text: "大小尺寸与商品描述不符"
+					},
+					{
+						value: 5,
+						text: "安装质量问题"
+					},
+					{
+						value: 6,
+						text: "做工瑕疵"
+					},
+					{
+						value: 7,
+						text: "颜色、款式、图案与描述不符"
+					},
+					{
+						value: 8,
+						text: "商品破损或污渍"
+					},
+					{
+						value: 9,
+						text: "其他原因"
+					},
+				],
+			}
+		},
+		onLoad(op) {
+			if (op.orderId) {
+				this.orderId = op.orderId
+				this.getDetail()
+				this.getOrderDetail()
+			}
+			if (op.id) {
+				this.getDetailById(op.id)
+			}
+			if (op.historyDetail) {
+				this.historyDetail = op.historyDetail
+			}
+			this.getAddress()
+		},
+		methods: {
+			getOrderDetail() {
+				orderDetail({
+					id: this.orderId
+				}).then((res) => {
+					this.order = res.data
+				})
+			},
+			afterRecord() {
+				this.$route('/pages/order/history-after?orderId=' + this.orderId);
+			},
+			submitAfterSale() {
+				this.$route('/pages/order/after?id=' + this.orderId);
+			},
+			detailPreviewImage(currentIndex) {
+				uni.previewImage({
+					current: this.imagesList[currentIndex], // 当前显示图片的链接
+					urls: this.imagesList // 需要预览的图片链接列表
+				});
+			},
+			// 计算并格式化倒计时
+			formatRemainingTime() {
+				const currentTime = new Date(); // 当前时间
+				const createTime = new Date(this.orderAfter.createTime.replace(/-/g, '/')); // 转换为 Date 对象
+				const deadline = new Date(createTime.getTime() + 7 * 24 * 60 * 60 * 1000); // 7天后的截止时间
+				console.log("currentTime", currentTime);
+				console.log("createTime", createTime);
+				console.log("deadline", deadline);
+
+				let remainingTime = deadline - currentTime; // 剩余时间(毫秒)
+
+				if (remainingTime <= 0) {
+					this.remainingTime = "已超过退货期限";
+					clearInterval(this.timer); // 清除定时器
+					return;
+				}
+
+				// 计算剩余天、小时和分钟
+				const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
+				const hours = Math.floor((remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+				const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60));
+				this.remainingTime = `${days}天${hours}小时${minutes}分钟`;
+			},
+			onCancelAfter() {
+				this.$refs.dialog.open()
+			},
+			cancel() {
+				cancelAfter({
+					id: this.orderAfter.id
+				}).then((res) => {
+					if (res.code == 200) {
+						this.$refs.customToast.showToast('操作成功');
+						this.$refs.dialog.close()
+						setTimeout(() => {
+							uni.navigateBack({
+								delta: 1
+							})
+						}, 1000)
+
+					}
+				})
+			},
+			// 启动倒计时
+			startCountdown() {
+				this.formatRemainingTime(); // 初始化倒计时
+
+				// 每分钟更新一次倒计时
+				this.timer = setInterval(() => {
+					this.formatRemainingTime();
+				}, 60000); // 每分钟更新一次
+			},
+
+			logistics() {
+				this.$route('/pages/order/logistics?expressCode=' + this.orderAfter.expressCode);
+			},
+			showExpressNumberPopup() {
+				this.$refs.expressNumberPopup.open("bottom");
+			},
+			hideExpressNumberPopup() {
+				this.$refs.expressNumberPopup.close();
+			},
+			submitExpressNumber() {
+				if (this.orderAfter.expressCode) {
+					updateExpressCode(this.orderAfter).then((res) => {
+						if (res.code == 200) {
+							this.$refs.customToast.showToast('提交成功');
+							this.getDetail();
+							this.hideExpressNumberPopup();
+						}
+					});
+				} else {
+					uni.showToast({
+						title: '请输入物流单号',
+						icon: 'none'
+					});
+				}
+			},
+			copyContent() {
+				uni.setClipboardData({
+					data: this.merchantAddress.address + " " + this.merchantAddress.mobile + " " + this
+						.merchantAddress.name,
+					success() {
+						// 复制成功的提示
+						uni.showToast({
+							title: '复制成功',
+							icon: 'success'
+						});
+					},
+				});
+			},
+			getDetail() {
+				orderAfterDetail({
+					orderId: this.orderId
+				}).then((res) => {
+					this.orderAfter = res.data
+					this.imagesList = this.orderAfter.images.split(',');
+					this.active = this.orderAfter.status
+					this.activeKey = Date.now(); // 更新 activeKey
+					this.$set(this, "active", this.orderAfter.status)
+					if (this.orderAfter.status == 1) {
+						this.startCountdown(); // 启动倒计时
+					}
+				})
+			},
+			getDetailById(id) {
+				orderAfterDetailById({
+					id: id
+				}).then((res) => {
+					this.orderAfter = res.data
+					this.orderId = this.orderAfter.orderId
+					this.getOrderDetail()
+					this.imagesList = this.orderAfter.images.split(',');
+					this.active = this.orderAfter.status
+					this.activeKey = Date.now(); // 更新 activeKey
+					this.$set(this, "active", this.orderAfter.status)
+					if (this.orderAfter.status == 1) {
+						this.startCountdown(); // 启动倒计时
+					}
+				})
+			},
+			getAddress() {
+				merchantAddressDetail().then((res) => {
+					this.merchantAddress = res.data
+				})
+			}
+		}
+	}
+</script>
+<style>
+	.navigation {
+		position: fixed;
+		bottom: 0;
+		width: 100%;
+		border: solid 2rpx #f2f2f2;
+		background-color: #ffffff;
+		padding: 16rpx 0;
+	}
+
+	button::after {
+		border: none;
+	}
+
+	.copy-btn {
+		background-color: #ffffff;
+		border: 1px solid #999999;
+		color: #333333;
+		padding: 5rpx 10rpx;
+		/* 减少按钮的内边距 */
+		border-radius: 8rpx;
+		font-size: 28rpx;
+	}
+
+	.productImg {
+		border-radius: 20rpx;
+		width: 120rpx;
+		height: 120rpx;
+	}
+
+	.grayBut {
+		background-color: #f2f2f2;
+		padding: 5rpx 30rpx 5rpx 30rpx;
+		font-size: 26rpx;
+		color: #1b1b1b;
+	}
+
+	.popup-content {
+		padding: 20rpx;
+	}
+
+	.popup-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		text-align: center;
+		margin-bottom: 20rpx;
+	}
+
+	.popup-input {
+		border: 1px solid #ccc;
+		border-radius: 8rpx;
+		margin-bottom: 20rpx;
+		height: 80rpx;
+	}
+
+	.popup-buttons {
+		display: flex;
+		justify-content: space-around;
+	}
+
+	.popup-cancel,
+	.popup-confirm {
+		padding: 10rpx 30rpx;
+		border-radius: 8rpx;
+		font-size: 28rpx;
+	}
+
+	.popup-cancel {
+		background-color: #f2f2f2;
+		color: #333;
+		width: 40%;
+	}
+
+	.popup-confirm {
+		background-color: #e29c6d;
+		color: #fff;
+		width: 40%;
+	}
+
+	.image-item {
+		margin-bottom: 10rpx;
+		display: flex;
+		/* 设置子元素垂直排列 */
+		align-items: center;
+		/* 水平居中对齐子元素 */
+	}
+
+	.images {
+		width: 100rpx;
+		height: 100rpx;
+	}
+</style>

+ 315 - 0
pages/order/after.vue

@@ -0,0 +1,315 @@
+<template>
+	<view>
+		<CustomToast ref="customToast" />
+		<!-- 页面标题 -->
+		<view class="flex-items region mar-t-10">
+			<view>
+				<image class=" mar-l-20 productImg" :src="order.images" mode="cover"></image>
+			</view>
+			<view class="mar-l-20 mar-r-20">
+				<view class="font28 pad-b-10 ">
+					{{order.productName}}
+				</view>
+				<view class="pad-tb-16 font-gray">
+					<text>
+						规格: {{order.skuName}}
+					</text>
+					<text class="mar-l-20">
+						x{{order.count}}
+					</text>
+				</view>
+
+				<view class="mar-r-16">
+				</view>
+			</view>
+		</view>
+		<view class="region1">
+			<uni-forms class="pad-16" ref="customForm" :rules="customRules" labelWidth="80px" :modelValue="after">
+				<uni-forms-item label="申请类型" required name="type">
+					<uni-data-select v-model="after.type" placeholder="请选择申请类型" :localdata="typeRange">
+					</uni-data-select>
+				</uni-forms-item>
+				<uni-forms-item label="申请原因" required name="reason">
+					<uni-data-select v-model="after.reason" placeholder="请选择申请原因" :localdata="reasonRange">
+					</uni-data-select>
+				</uni-forms-item>
+			</uni-forms>
+		</view>
+		<view class="region pad-16">
+			<view class="flex-items-between">
+				<view>
+					退货数量(本次可申请{{order.count}}件)
+				</view>
+				<view>
+					<uni-number-box v-model="after.count" :min="1" :max="order.count" />
+				</view>
+			</view>
+			<view class="flex-items-between mar-t-20">
+				<view>
+					退款金额
+				</view>
+				<view>
+					<text class=" font-red font32 font-bold">
+						¥{{order.unitPrice*after.count}}
+					</text>
+				</view>
+			</view>
+		</view>
+		<!-- 表单区域 -->
+		<view class="form-container region">
+			<!-- 申请说明 -->
+			<view class="form-item">
+				<view class="form-label">申请说明</view>
+				<view class="textarea-container">
+					<textarea style="width: 100%;" v-model="after.remark" placeholder="请详细描述申请说明(选填)"
+						class="form-textarea" :maxlength="maxLength"></textarea>
+					<view class="char-count">
+						您还可以输入{{ remainingChars }} 字
+					</view>
+				</view>
+			</view>
+
+			<!-- 上传凭证 -->
+			<view class="form-item">
+				<view class="form-label">上传凭证(可选)</view>
+				<view class="upload-container">
+					<uni-file-picker @delete="delFile" v-model="imageValue" fileMediatype="image" mode="grid"
+						@select="select" />
+				</view>
+			</view>
+		</view>
+
+		<!-- 提交按钮 -->
+		<button class="submit-button" @click="submitForm('customForm')">提交申请</button>
+	</view>
+</template>
+
+<script>
+	import CustomToast from '../../components/CustomToast.vue';
+
+	import {
+		UPLOAD_URL
+	} from '../../common/config.js';
+	import {
+		orderDetail,
+		submitOrderAfter
+	} from '../../config/api.js';
+
+	export default {
+		components: {
+			CustomToast,
+		},
+		data() {
+			return {
+				typeRange: [{
+						value: 1,
+						text: "退货退款"
+					},
+					{
+						value: 2,
+						text: "仅退款"
+					},
+				],
+				reasonRange: [{
+						value: 1,
+						text: "不喜欢,效果不好"
+					},
+					{
+						value: 2,
+						text: "不想要了"
+					},
+					{
+						value: 3,
+						text: "材质与商品描述不符"
+					},
+					{
+						value: 4,
+						text: "大小尺寸与商品描述不符"
+					},
+					{
+						value: 5,
+						text: "安装质量问题"
+					},
+					{
+						value: 6,
+						text: "做工瑕疵"
+					},
+					{
+						value: 7,
+						text: "颜色、款式、图案与描述不符"
+					},
+					{
+						value: 8,
+						text: "商品破损或污渍"
+					},
+					{
+						value: 9,
+						text: "其他原因"
+					},
+				],
+				after: {
+					imgList: [],
+					reason: '',
+					remark: "",
+					count: 1
+				},
+				customRules: {
+					type: {
+						rules: [{
+							required: true,
+							errorMessage: '请选择申请类型'
+						}]
+					},
+					reason: {
+						rules: [{
+							required: true,
+							errorMessage: '请选择申请原因'
+						}]
+					},
+				},
+				token: '',
+				imageValue: [],
+				fileList: [],
+				orderNumber: '',
+				reason: '',
+				images: [],
+				contactName: '',
+				contactPhone: '',
+				orderId: "",
+				order: {},
+				maxLength: 200, // 最大字数
+			};
+		},
+		computed: {
+			// 计算剩余字数
+			remainingChars() {
+				return this.maxLength - this.after.remark.length;
+			}
+		},
+		onLoad(op) {
+			this.orderId = op.id;
+			this.getOrderDetail();
+			this.token = uni.getStorageSync('access_token');
+		},
+		methods: {
+			delFile(e) {
+				this.after.imgList.splice(e.index, 1); // 删除对应 index 的附件
+			},
+			getOrderDetail() {
+				orderDetail({
+					id: this.orderId
+				}).then((res) => {
+					this.order = res.data;
+				});
+			},
+			// 获取选择的文件
+			async select(e) {
+				let list = [].concat(e.tempFiles);
+				for (let i = 0; i < list.length; i++) {
+					const result = await this.uploadFilePromise(list[i].url);
+					this.after.imgList.push(result.data.link);
+				}
+			},
+			uploadFilePromise(url) {
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: UPLOAD_URL,
+						filePath: url,
+						header: {
+							"Blade-Auth": this.token
+						},
+						name: 'file',
+						formData: {
+							user: 'test'
+						},
+						success: (res) => {
+							resolve(JSON.parse(res.data));
+						}
+					});
+				});
+			},
+			// 提交表单
+			submitForm(ref) {
+				this.$refs[ref].validate().then(res => {
+					this.after.images = this.after.imgList.join(',');
+					this.after.orderId = this.orderId
+					submitOrderAfter(this.after).then((res) => {
+						if (res.code == 200) {
+							this.$refs.customToast.showToast('申请提交成功');
+							setTimeout(() => {
+								uni.navigateBack({
+									delta: 1
+								})
+							}, 1000)
+						}
+
+
+					})
+				})
+
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.page-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		text-align: center;
+		padding: 20rpx 0;
+		color: #333;
+	}
+
+	.form-container {
+		padding: 20rpx;
+	}
+
+	.form-item {
+
+		margin-bottom: 20rpx;
+	}
+
+	.form-label {
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 10rpx;
+	}
+
+	.textarea-container {
+		position: relative;
+	}
+
+	.form-textarea {
+		border: 1px solid #ccc;
+		border-radius: 5rpx;
+		padding: 10rpx;
+		font-size: 28rpx;
+		height: 150rpx;
+		resize: none;
+	}
+
+	.char-count {
+		position: absolute;
+		bottom: 10rpx;
+		right: 10rpx;
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.upload-container {
+		margin-top: 10rpx;
+	}
+
+	.submit-button {
+		background-color: #F95B5B;
+		color: #fff;
+		font-size: 32rpx;
+		padding: 5rpx;
+		border-radius: 10rpx;
+		margin-left: 30rpx;
+		margin-right: 30rpx;
+		text-align: center;
+		margin-top: 30rpx;
+	}
+</style>

+ 439 - 0
pages/order/detail.vue

@@ -0,0 +1,439 @@
+<template>
+	<view>
+		<CustomToast ref="customToast" />
+		<view class="region  pad-24 mar-t-10" v-if="order.isAfter==3">
+			<view @tap="checkAfterSaleStatus" class="flex-items-between">
+				<view>
+					<view class="font-red">
+						售后已关闭
+					</view>
+					<view v-if="orderAfter.status==4">
+						<view class="font38 font-bold ">
+							商家审核不通过
+						</view>
+						<view class="font28">
+							原因:{{orderAfter.approveRemark}}
+						</view>
+					</view>
+					<view v-if="orderAfter.status==5">
+						<view class="font38 font-bold ">
+							商家不同意退款
+						</view>
+						<view class="font28">
+							原因:{{orderAfter.approveRemark}}
+						</view>
+					</view>
+					<view v-if="orderAfter.status==6">
+						<view class="font38 font-bold ">
+							您已取消售后
+						</view>
+
+					</view>
+				</view>
+				<uni-icons color="#a8aeb4" type="right" size="20"></uni-icons>
+			</view>
+		</view>
+		<view class="region pad-24 mar-t-10">
+			<view v-if="order.isAfter==0||order.isAfter==3">
+				<!-- 待付款状态 -->
+				<view v-if="order.status==1">
+					<view class="font38 font-bold ">
+						订单待付款,点击立即付款
+					</view>
+				</view>
+				<!-- 待发货状态 -->
+				<view v-if="order.status==2" class="flex-items-plus ">
+					<uni-icons color="#63b43d" type="checkbox-filled" size="30"></uni-icons>
+					<view class="font34 font-bold">
+						买家已付款
+					</view>
+				</view>
+				<!-- 待收货状态 -->
+				<view v-if="order.status==3" class="flex-items-between" @tap="checkLogistics">
+					<view class="font34 font-bold ">
+						订单已发货,点击查看物流
+					</view>
+					<uni-icons color="#a8aeb4" type="right" size="20"></uni-icons>
+				</view>
+				<!-- 已完成状态 -->
+				<view v-if="order.status==4">
+					<!-- 是否已评价  0否,1是-->
+					<view class="flex-items-between ">
+						<view class="flex-items">
+							<uni-icons color="#63b43d" type="checkbox-filled" size="30"></uni-icons>
+							<view class="font34 font-bold">
+								订单已完成
+							</view>
+						</view>
+						<view class="flex-items font-blue font28" @tap="goScore" v-if="order.isEvaluate==0">
+							去评价
+							<uni-icons color="#646464" type="right" size="20"></uni-icons>
+						</view>
+					</view>
+
+				</view>
+
+			</view>
+			<view v-if="order.isAfter==1">
+				<!-- 售后状态 -->
+				<view class="flex-items-between mar-t-10" @tap="checkAfterSaleStatus">
+					<view class="font38 font-bold " v-if="orderAfter.status==0">
+						售后已提交,请等待商家审核
+					</view>
+					<view class="font38 font-bold " v-if="orderAfter.status==1">
+						商家已审核通过,请寄回商品
+					</view>
+					<view class="font38 font-bold " v-if="orderAfter.status==2">
+						商品已寄出,等待商家退款
+					</view>
+					<view>
+						<uni-icons color="#7f8388" type="right" size="20"></uni-icons>
+					</view>
+				</view>
+			</view>
+			<view v-if="order.isAfter==2">
+				<view v-if="orderAfter.status==3">
+					<view class="flex-items-plus">
+						<uni-icons color="#63b43d" type="checkbox-filled" size="30"></uni-icons>
+						<view class="font38 font-bold ">
+							售后完成
+						</view>
+					</view>
+					<view class="text-align mar-t-10">
+						钱款{{orderAfter.afterPrice}}元已退回到你**账户
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="region pad-24 mar-t-10">
+			<view class="flex-items-between">
+				<view class="flex-items">
+					<uni-icons type="location" size="30"></uni-icons>
+					<view class="font-bold">
+						{{order.province}}{{order.city}}{{order.area}}{{order.address}}
+					</view>
+				</view>
+				<view class="font-blue" @tap="updateAddress"
+					v-if="(order.isAfter==0||order.isAfter==3)&&order.status==2">
+					修改>
+				</view>
+			</view>
+
+			<view class="mar-t-10 mar-l-40">
+				{{order.name}} {{order.mobile}}
+			</view>
+		</view>
+		<view class="gray-line"></view>
+		<view class="pad-24 region">
+			<view class="flex-items " @tap="goProduct">
+				<view>
+					<image class=" mar-l-20 productImg" :src="order.images" mode="cover"></image>
+				</view>
+				<view class="mar-l-20 mar-r-20">
+					<view class="font28 pad-b-10 ">
+						{{order.productName}}
+					</view>
+					<view class="pad-tb-16 font-gray">
+						<text>
+							规格: {{order.skuName}}
+						</text>
+						<text class="mar-l-20">
+							x{{order.count}}
+						</text>
+					</view>
+					<view class="pad-t-10  ">
+						<text class=" font-red font32 font-bold">
+							¥{{order.paymentPrice}}
+						</text>
+					</view>
+					<view class="mar-r-16">
+					</view>
+				</view>
+			</view>
+			<view class="gray-line"></view>
+			<view class="pad-24">
+				<view class="flex-items-between">
+					<text>
+						商品总价
+					</text>
+					<text>
+						{{order.paymentPrice}}
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40">
+					<text>
+						运费
+					</text>
+					<text>
+						0
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40">
+					<text>
+						实付款
+					</text>
+					<text>
+						{{order.paymentPrice}}
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40">
+					<text>
+						订单编号
+					</text>
+					<text>
+						{{order.orderCode}}
+					</text>
+				</view>
+				<!-- <view class="flex-items-between mar-t-40">
+					<text>
+						收货信息
+					</text>
+					<view>
+						<view>
+							{{order.province}}{{order.city}}{{order.area}}{{order.address}}
+						</view>
+						<view class="mar-t-10">
+							{{order.mobile}} {{order.name}}
+						</view>
+
+					</view>
+				</view> -->
+				<view class="flex-items-between mar-t-40">
+					<text>
+						创建时间
+					</text>
+					<text>
+						{{order.createTime}}
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40">
+					<text>
+						付款时间
+					</text>
+					<text>
+						{{order.payTime}}
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40" v-if="order.status==3||order.status==4">
+					<text>
+						发货时间
+					</text>
+					<text>
+						{{order.shipTime}}
+					</text>
+				</view>
+				<view class="flex-items-between mar-t-40" v-if="order.status==4">
+					<text>
+						成交时间
+					</text>
+					<text>
+						{{order.confirmTime}}
+					</text>
+				</view>
+			</view>
+		</view>
+		<view class="navigation">
+			<view class="flex-items-between">
+				<view class="mar-l-20" @click="$Router.pushTab({name: 'index'})">
+					<u-icon name="chat" color="#F95B5B" size="22"></u-icon>
+					<view>客服</view>
+				</view>
+				<view>
+					<view v-if="order.isAfter==0||order.isAfter==3">
+						<!-- 待付款状态 -->
+						<view class="flex-items" v-if="order.status === 1">
+							<button class="grayBut mar-r-20" @click="cancelOrder">取消订单</button>
+							<button class="orgBut mar-r-20" @click="payOrder">去付款</button>
+						</view>
+
+						<!-- 待发货状态 -->
+						<view class="flex-items" v-if="order.status === 2">
+							<button class="grayBut mar-r-20" @click="submitAfterSale">申请售后</button>
+							<button class="orgBut mar-r-20" @click="onceAgainOrder">再来一单</button>
+						</view>
+
+						<!-- 待收货状态 -->
+						<view class="flex-items" v-if="order.status === 3">
+							<button class="grayBut mar-r-20" @click="submitAfterSale">申请售后</button>
+							<button class="grayBut mar-r-20" @click="checkLogistics">查看物流</button>
+							<button class="orgBut mar-r-20" @click="confirmReceiptDialog">确认收货</button>
+						</view>
+
+						<!-- 已完成状态 -->
+						<view class="flex-items" v-if="order.status === 4">
+							<button class="grayBut mar-r-20" @click="deleteOrder">删除订单</button>
+							<button class="grayBut mar-r-20" @click="submitAfterSale">申请售后</button>
+							<button class="orgBut mar-r-20" @click="onceAgainOrder">再来一单</button>
+						</view>
+					</view>
+
+					<!-- 售后中状态 -->
+					<view v-if="order.isAfter === 1" class="flex-items">
+						<button class="grayBut mar-r-20" @click="onCancelAfter">取消售后</button>
+						<button class="orgBut mar-r-20" @click="checkAfterSaleStatus">查看售后进度</button>
+					</view>
+
+					<!-- 售后完成状态 -->
+					<view v-if="order.isAfter === 2" class="flex-items">
+						<button class="grayBut mar-r-20" @click="checkAfterSaleStatus">售后详情</button>
+						<button class="orgBut mar-r-20" @click="onceAgainOrder">再来一单</button>
+					</view>
+
+				</view>
+			</view>
+		</view>
+		<uni-popup ref="dialog" type="dialog">
+			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确认" title="提示" content="确认要取消售后?"
+				@confirm="cancel()"></uni-popup-dialog>
+		</uni-popup>
+		<uni-popup ref="dialog" type="dialog">
+			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确认" title="提示" content="确认收货?"
+				@confirm="confirmReceipt()"></uni-popup-dialog>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		orderDetail,
+		cancelAfter,
+		orderAfterDetail,
+		confirmReceipt
+	} from '../../config/api.js';
+	import CustomToast from '../../components/CustomToast.vue';
+	export default {
+		components: {
+			CustomToast,
+		},
+		data() {
+			return {
+				orderAfter: {},
+				orderId: "",
+				order: {},
+				form: {},
+				isFirstLoad: true, // 新增标志位,用于判断是否是首次加载
+			};
+		},
+		methods: {
+			goScore() {
+				this.$route('/pages/order/score?orderId=' + this.order.id)
+			},
+			updateAddress() {
+				this.$route('/pages/order/update-address?orderId=' + this.order.id)
+			},
+			cancel() {
+				cancelAfter({
+					id: this.orderAfter.id
+				}).then((res) => {
+					if (res.code == 200) {
+						this.$refs.customToast.showToast('操作成功');
+						this.$refs.dialog.close()
+						this.getOrderDetail();
+					}
+				})
+			},
+			onCancelAfter() {
+				this.$refs.dialog.open()
+			},
+			goProduct() {
+				this.$route('/packageShop/pages/detail/index?id=' + this.order.productId)
+			},
+			getOrderDetail() {
+				orderDetail({
+					id: this.orderId
+				}).then((res) => {
+					this.order = res.data;
+					this.getAfterDetail()
+				});
+			},
+			getAfterDetail() {
+				orderAfterDetail({
+					orderId: this.orderId
+				}).then((res) => {
+					this.orderAfter = res.data
+				})
+			},
+			cancelOrder() {
+				console.log('取消订单,订单号:', this.order.orderCode);
+			},
+			payOrder() {
+				// 跳转到支付页面
+				this.$route('/pages/order/payment?orderId=' + this.order.id);
+			},
+			submitAfterSale() {
+				this.$route('/pages/order/after?id=' + this.order.id);
+			},
+
+			checkLogistics() {
+				this.$route('/pages/order/logistics?expressCode=' + this.order.expressCode);
+			},
+			confirmReceiptDialog() {
+				this.$refs.dialog.open()
+				this.form = this.order
+			},
+			confirmReceipt() {
+				confirmReceipt(this.form).then((res) => {
+					if (res.code == 200) {
+						this.$refs.dialog.close()
+						this.$refs.customToast.showToast('操作成功');
+						this.getOrderDetail()
+					}
+				})
+			},
+			deleteOrder() {
+				console.log('删除订单,订单号:', this.order.orderCode);
+			},
+			addToCart() {
+				console.log('加入购物车,订单号:', this.order.orderCode);
+			},
+			onceAgainOrder() {
+				console.log('再来一单,订单号:', this.order.orderCode);
+			},
+			checkAfterSaleStatus() {
+				this.$route('/pages/order/after-progress?id=' + this.orderAfter.id);
+			}
+		},
+		onLoad(op) {
+			this.orderId = op.id;
+			this.getOrderDetail();
+			setTimeout(() => {
+				this.isFirstLoad = false; // 首次加载完成,将标志位设为 false
+			}, 1000)
+		},
+		onShow() {
+			if (!this.isFirstLoad) {
+				// 如果不是首次加载,再调用  方法
+				this.getOrderDetail();
+			}
+		}
+	};
+</script>
+
+<style>
+	.navigation {
+		position: fixed;
+		bottom: 0;
+		width: 100%;
+		border: solid 2rpx #f2f2f2;
+		background-color: #ffffff;
+		padding: 16rpx 0;
+	}
+
+	.grayBut {
+		background-color: #f2f2f2;
+		padding: 5rpx 30rpx 5rpx 30rpx;
+		font-size: 26rpx;
+		color: #1b1b1b;
+	}
+
+	.orgBut {
+		background-color: #F95B5B;
+		padding: 5rpx 30rpx 5rpx 30rpx;
+		font-size: 26rpx;
+		color: #ffffff;
+	}
+
+	button::after {
+		border: none;
+	}
+</style>

+ 255 - 0
pages/order/history-after.vue

@@ -0,0 +1,255 @@
+<template>
+	<view class="bg">
+		<view v-if="orderList.length>0">
+			<view v-for="(item,index) in orderList" :key="index">
+				<view class="info-region">
+					<view>
+						<view class="flex-items flex-sp-between mar-b-20 mar-lr-12">
+							<view>
+								订单号:{{item.orderCode}}
+							</view>
+
+						</view>
+						<view class="flex">
+							<view>
+								<image class=" mar-l-20 img" :src="item.productImg" mode="aspectFit"></image>
+							</view>
+							<view class="mar-l-20 mar-r-20">
+								<view class="font32 pad-b-10 ">
+									{{item.productName}}
+								</view>
+								<view class="pad-t-20 font-gray">
+									规格: {{item.skuName}}
+								</view>
+								<view class="pad-t-10 font-gray">
+									退货类型:
+									{{item.type==1?'退货退款':'仅退款'}}
+								</view>
+								<view class="pad-t-10 font-gray">
+									退货原因:{{reasonRange[item.reason].text}}
+								</view>
+							</view>
+						</view>
+
+
+						<text class="flex-end font-red font28 font-bold mar-r-10">
+							退款金额 ¥{{item.afterPrice}}
+						</text>
+					</view>
+
+
+					<!-- 售后中状态 -->
+					<view class=" flex-items flex-end mar-t-20 mar-r-10">
+						<view class="cancelBtn" @click="checkAfterSaleStatus(item)">
+							查看详情
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else>
+			<view class="flex-items-plus">
+				<image src="../../static/images/empty.png" class="empty "></image>
+			</view>
+			<view class="font28 font-gray flex-items-plus">
+				数据为空
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getAfterList
+	} from '../../config/api.js';
+
+	export default {
+		components: {},
+		data() {
+			return {
+				typeRange: [{
+						value: 1,
+						text: "退货退款"
+					},
+					{
+						value: 2,
+						text: "仅退款"
+					},
+				],
+				reasonRange: [{
+						value: 1,
+						text: "不喜欢,效果不好"
+					},
+					{
+						value: 2,
+						text: "不想要了"
+					},
+					{
+						value: 3,
+						text: "材质与商品描述不符"
+					},
+					{
+						value: 4,
+						text: "大小尺寸与商品描述不符"
+					},
+					{
+						value: 5,
+						text: "安装质量问题"
+					},
+					{
+						value: 6,
+						text: "做工瑕疵"
+					},
+					{
+						value: 7,
+						text: "颜色、款式、图案与描述不符"
+					},
+					{
+						value: 8,
+						text: "商品破损或污渍"
+					},
+					{
+						value: 9,
+						text: "其他原因"
+					},
+				],
+				current: 0,
+				orderId: "",
+				orderList: [],
+				params: {
+					current: 1,
+					size: 10,
+					status: "",
+					isAfter: "",
+					type: "",
+					orderId: ""
+
+				}
+			};
+		},
+		onLoad(op) {
+			this.params.orderId = op.orderId
+			this.getList();
+
+		},
+		onPullDownRefresh() {
+			this.orderList = [];
+			this.params.current = 1;
+			this.getList();
+			uni.stopPullDownRefresh();
+		},
+		methods: {
+			goDetail(item) {
+				this.$route('/pages/order/detail?id=' + item.id);
+			},
+			checkAfterSaleStatus(item) {
+				// 实现查看售后进度逻辑
+				this.$route('/pages/order/after-progress?id=' + item.id + "&historyDetail=1");
+			},
+			// 订单列表
+			getList() {
+				getAfterList(this.params).then((res) => {
+					this.orderList = res.data.records;
+				});
+			},
+		}
+	};
+</script>
+
+<style lang="scss">
+	.bg {
+		min-height: 100vh;
+		padding-bottom: 20rpx;
+		// background: linear-gradient(135deg, #f8ffee 0%, #fffff6 100%);
+	}
+
+	.info-region {
+		/* 阴影效果 */
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		border-radius: 15rpx;
+		width: 720rpx;
+		height: auto;
+		padding: 15rpx;
+		background-color: white;
+		margin-top: 10rpx;
+		margin-bottom: 20rpx;
+		margin-left: 15rpx;
+	}
+
+	.img {
+		border-radius: 20rpx;
+		width: 200rpx;
+		height: 200rpx;
+	}
+
+	.daifahuo {
+		color: #ffffff;
+		background-color: #e2c696;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.fukuan {
+		color: #ffffff;
+		background-color: #e29c6d;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhuo {
+		color: #ffffff;
+		background-color: #e2d972;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.wancheng {
+		color: #ffffff;
+		background-color: #83e25a;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.pingjia {
+		color: #ffffff;
+		background-color: #e0e2d9;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhou {
+		color: #ffffff;
+		background-color: #e23e3e;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.buyBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #fff;
+		background: #E2A28A;
+	}
+
+	.cancelBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #6a6a6a;
+		background: #e2e2e2;
+	}
+
+	.custom-easyinput {
+		border: none;
+		/* 取消边框 */
+		outline: none;
+		/* 取消聚焦时的轮廓线 */
+	}
+</style>

+ 427 - 0
pages/order/list.vue

@@ -0,0 +1,427 @@
+<template>
+	<view class="bg">
+		<u-sticky>
+			<view class="pad-t-10 ">
+				<view class="flex-items bg-white pad-16" @tap="goSearch">
+					<uni-icons type="search" color="#a8a8a8" size="28"></uni-icons>
+
+					<view v-if="params.orderCode" class="font28">
+						{{params.orderCode}}
+					</view>
+					<view class="font-gray font28" v-else>
+						请输入订单号或商品名称
+					</view>
+				</view>
+			</view>
+			<view class="bg-white">
+				<u-tabs :current="current" lineWidth="30" :list="tabList" @click="tabClick"></u-tabs>
+			</view>
+		</u-sticky>
+		<view v-if="orderList.length>0">
+			<view v-for="(item,index) in orderList" :key="index">
+				<view class="info-region">
+					<view @tap="goDetail(item)">
+						<view class="flex-items flex-sp-between mar-b-20 mar-lr-12">
+							<view>
+								订单号:{{item.orderCode}}
+							</view>
+							<view v-if="item.isAfter==0||item.isAfter==3">
+								<text class="fukuan" v-if="item.status==1">
+									待付款
+								</text>
+								<text class="daifahuo" v-if="item.status==2">
+									待发货
+								</text>
+								<text class="shouhuo" v-if="item.status==3">
+									待收货
+								</text>
+								<text class="pingjia mar-r-10" v-if="item.status==4&&item.isEvaluate==0">
+									待评价
+								</text>
+								<text class="wancheng" v-if="item.status==4">
+									已完成
+								</text>
+							</view>
+							<text class="shouhou" v-if="item.isAfter==1">
+								售后中
+							</text>
+							<text class="pingjia" v-if="item.isAfter==2">
+								售后完成
+							</text>
+
+						</view>
+						<view class="flex">
+							<view>
+								<image class=" mar-l-20 img" :src="item.images" mode="aspectFit"></image>
+							</view>
+							<view class="mar-l-20 mar-r-20">
+								<view class="font32 pad-b-10 ">
+									{{item.productName}}
+								</view>
+								<view class="pad-t-20 font-gray">
+									规格: {{item.skuName}}
+								</view>
+							</view>
+						</view>
+						<text class="flex-end font-red font28 font-bold mar-r-10">
+							实付款 ¥{{item.paymentPrice}}
+						</text>
+					</view>
+					<view>
+						<view v-if="item.isAfter===0||item.isAfter===0">
+							<!-- 待付款状态 -->
+							<view v-if="item.status === 1" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="payOrder(item)">
+										去付款
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="cancelOrder(item)">
+										取消订单
+									</view>
+								</view>
+							</view>
+							<!-- 待发货状态 -->
+							<view v-if="item.status === 2" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="after(item)">
+										申请售后
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10" v-if="item.status === 2">
+									<view class="buyBtn" @click="onceOrder(item)">
+										再来一单
+									</view>
+								</view>
+							</view>
+
+							<!-- 待收货状态 -->
+							<view v-if="item.status === 3" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="logistics(item)">
+										查看物流
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="after(item)">
+										申请售后
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="confirmReceiptDialog(item)">
+										确认收货
+									</view>
+								</view>
+							</view>
+
+							<!-- 已完成状态 -->
+							<view v-if="item.status === 4" class="flex-items flex-end">
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="goScore(item)" v-if="item.isEvaluate==0">
+										评价
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="cancelBtn" @click="after(item)">
+										申请售后
+									</view>
+								</view>
+								<view class=" mar-t-20 mar-r-10">
+									<view class="buyBtn" @click="onceOrder(item)">
+										再来一单
+									</view>
+								</view>
+							</view>
+
+						</view>
+
+						<!-- 售后中状态 -->
+						<view class=" flex-items flex-end mar-t-20 mar-r-10" v-if="item.isAfter === 1">
+							<view class="cancelBtn" @click="checkAfterSaleStatus(item)">
+								售后进度
+							</view>
+						</view>
+						<!-- 售后完成状态 -->
+						<view class=" flex-items flex-end mar-t-20 mar-r-10" v-if="item.isAfter === 2">
+							<view class="cancelBtn" @click="checkAfterSaleStatus(item)">
+								售后详情
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else>
+			<view class="flex-items-plus">
+				<image src="../../static/images/empty.png" class="empty "></image>
+			</view>
+			<view class="font28 font-gray flex-items-plus">
+				数据为空
+			</view>
+		</view>
+		<CustomToast ref="customToast" />
+		<uni-popup ref="dialog" type="dialog">
+			<uni-popup-dialog type="warn" cancelText="取消" confirmText="确认" title="提示" content="确认收货?"
+				@confirm="confirmReceipt()"></uni-popup-dialog>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+	import CustomToast from '../../components/CustomToast.vue';
+	import {
+		getOrderList,
+		confirmReceipt
+	} from '../../config/api.js';
+
+	export default {
+		components: {
+			CustomToast,
+		},
+		data() {
+			return {
+				current: 0,
+				searchKey: "",
+				tabList: [{
+						name: '全部'
+					},
+					{
+						name: '待付款'
+					},
+					{
+						name: '待发货'
+					},
+					{
+						name: '待收货'
+					},
+					{
+						name: '已完成'
+					},
+					{
+						name: '售后'
+					},
+					{
+						name: '待评价'
+					}
+				],
+				orderList: [],
+				isFirstLoad: true, // 新增标志位,用于判断是否是首次加载
+				params: {
+					current: 1,
+					size: 10,
+					status: "",
+					isAfter: "",
+					isEvaluate: "",
+					type: "",
+					orderCode: ""
+				},
+				form: {},
+			};
+		},
+		onLoad(op) {
+			if (op.serachKey) {
+				this.params.orderCode = op.serachKey;
+			}
+			console.log(op);
+			if (op.status && op.status != 0) {
+				this.params.status = op.status;
+				this.current = op.status;
+			}
+			if (op.isAfter) {
+				this.params.isAfter = op.isAfter;
+				this.current = 5;
+			}
+			if (op.isEvaluate) {
+				this.params.isEvaluate = op.isEvaluate;
+				this.current = 6;
+			}
+			this.getList();
+			setTimeout(() => {
+				this.isFirstLoad = false; // 首次加载完成,将标志位设为 false
+			}, 1000)
+		},
+		onPullDownRefresh() {
+			this.reset()
+			uni.stopPullDownRefresh();
+		},
+		onShow() {
+			if (!this.isFirstLoad) {
+				// 如果不是首次加载,再调用 getList 方法
+				this.getList();
+			}
+		},
+		methods: {
+			goScore(item) {
+				this.$route('/pages/order/score?orderId=' + item.id)
+			},
+			reset() {
+				this.orderList = [];
+				this.params.current = 1;
+				this.getList();
+			},
+			after(item) {
+				this.$route('/pages/order/after?id=' + item.id);
+			},
+			logistics(item) {
+				this.$route('/pages/order/logistics?expressCode=' + item.expressCode);
+			},
+			onceOrder(item) {
+				console.log(item, "item");
+				this.$route('/packageShop/pages/detail/index?skuId=' + item.skuId + '&id=' + item.productId);
+			},
+			goSearch() {
+				this.$route('/pages/order/search?type=2');
+			},
+			goDetail(item) {
+				this.$route('/pages/order/detail?id=' + item.id);
+			},
+			tabClick(item) {
+				console.log(item);
+				if (item.index == 6) {
+					this.params.isEvaluate = 0;
+					this.params.isAfter = ""
+					this.params.status = ""
+				} else if (item.index == 5) {
+					this.params.isAfter = 1
+					this.params.status = ""
+					this.params.isEvaluate = ""
+				} else {
+					this.params.status = item.index;
+					this.params.isAfter = ""
+					this.params.isEvaluate = ""
+				}
+				this.getList();
+			},
+			// 订单列表
+			getList() {
+				getOrderList(this.params).then((res) => {
+					this.orderList = res.data.records;
+				});
+			},
+			payOrder(item) {
+				// 跳转到支付页面
+				this.$route('/pages/order/payment?orderId=' + item.id);
+			},
+			cancelOrder(item) {
+				// 实现取消订单逻辑
+				console.log('取消订单,订单号:', item.orderCode);
+			},
+			confirmReceiptDialog(item) {
+				this.$refs.dialog.open()
+				this.form = item
+			},
+			confirmReceipt() {
+				confirmReceipt(this.form).then((res) => {
+					if (res.code == 200) {
+						this.$refs.dialog.close()
+						this.$refs.customToast.showToast('操作成功');
+						this.reset()
+					}
+				})
+			},
+			checkAfterSaleStatus(item) {
+				// 实现查看售后进度逻辑
+				this.$route('/pages/order/after-progress?orderId=' + item.id);
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.bg {
+		min-height: 100vh;
+		padding-bottom: 20rpx;
+		// background: linear-gradient(135deg, #f8ffee 0%, #fffff6 100%);
+	}
+
+	.info-region {
+		/* 阴影效果 */
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		border-radius: 15rpx;
+		width: 720rpx;
+		height: auto;
+		padding: 15rpx;
+		background-color: white;
+		margin-top: 10rpx;
+		margin-bottom: 20rpx;
+		margin-left: 15rpx;
+	}
+
+	.img {
+		border-radius: 20rpx;
+		width: 200rpx;
+		height: 200rpx;
+	}
+
+	.daifahuo {
+		color: #ffffff;
+		background-color: #e2c696;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.fukuan {
+		color: #ffffff;
+		background-color: #e29c6d;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhuo {
+		color: #ffffff;
+		background-color: #e2d972;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.wancheng {
+		color: #ffffff;
+		background-color: #83e25a;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.pingjia {
+		color: #ffffff;
+		background-color: #e0e2d9;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.shouhou {
+		color: #ffffff;
+		background-color: #e23e3e;
+		padding: 4rpx 10rpx 4rpx 10rpx;
+		border-radius: 20rpx;
+	}
+
+	.buyBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #fff;
+		background: #E2A28A;
+	}
+
+	.cancelBtn {
+		font-size: 30rpx;
+		width: 160rpx;
+		height: 50rpx;
+		text-align: center;
+		line-height: 50rpx;
+		border-radius: 36rpx;
+		color: #6a6a6a;
+		background: #e2e2e2;
+	}
+
+	.custom-easyinput {
+		border: none;
+		/* 取消边框 */
+		outline: none;
+		/* 取消聚焦时的轮廓线 */
+	}
+</style>

+ 109 - 0
pages/order/logistics.vue

@@ -0,0 +1,109 @@
+<template>
+	<view class="container">
+		<!-- 物流头部信息 -->
+		<view class="logistics-header">
+
+			<view class="flex-items-between">
+				<view class="font28">物流公司: {{ logisticsCompany }}</view>
+				<view>
+					<text class="font28 mar-r-10">单号: {{ logisticsNumber }}</text>
+					<text class="copy-btn" @tap="copyContent">复制</text>
+				</view>
+
+
+			</view>
+		</view>
+		<!-- 物流轨迹列表 -->
+		<view class="logistics-trace">
+			<uni-steps :options="logisticsList" direction="column" :active="0"></uni-steps>
+		</view>
+
+
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				logisticsCompany: '顺丰速运',
+				logisticsNumber: '',
+				logisticsList: [{
+						desc: '2025-03-25 10:00:00',
+						title: '【深圳转运中心】已发出,下一站【北京转运中心】'
+					},
+					{
+						desc: '2025-03-24 15:30:00',
+						title: '【深圳站点】已收件'
+					},
+					{
+						desc: '2025-03-24 13:00:00',
+						title: '卖家已发货'
+					}
+				]
+			};
+		},
+		methods: {
+			copyContent() {
+				uni.setClipboardData({
+					data: this.logisticsNumber,
+					success: () => {
+						uni.showToast({
+							title: '复制成功',
+							icon: 'none'
+						});
+					}
+				});
+			}
+		},
+		onLoad(op) {
+			this.logisticsNumber = op.expressCode
+		}
+	};
+</script>
+
+<style scoped>
+	.container {
+		padding: 20rpx;
+		background-color: #f8f8f8;
+	}
+
+	.logistics-header {
+		background-color: #ffffff;
+		border-radius: 10rpx;
+		padding: 32rpx;
+		box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+		margin-bottom: 20rpx;
+	}
+
+	.company-logo {
+		width: 80rpx;
+		height: 80rpx;
+		margin-right: 20rpx;
+	}
+
+	.company-logo image {
+		width: 100%;
+		height: 100%;
+	}
+
+
+	.logistics-trace {
+		min-height: calc(100vh - 20rpx - 32rpx - 20rpx - 20rpx);
+		/* 减去容器内边距、头部内边距、头部底部外边距和自身内边距 */
+		background-color: #ffffff;
+		border-radius: 10rpx;
+		padding: 20rpx;
+		box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+	}
+
+	.copy-btn {
+		background-color: #ffffff;
+		border: 1px solid #999999;
+		color: #333333;
+		padding: 5rpx 10rpx;
+		/* 减少按钮的内边距 */
+		border-radius: 8rpx;
+		font-size: 28rpx;
+	}
+</style>

+ 359 - 0
pages/order/payment.vue

@@ -0,0 +1,359 @@
+<template>
+	<view class="payment-container">
+		<!-- 收货地址 -->
+		<view class="address-section">
+			<view class="address-content" v-if="orderInfo">
+				<view class="contact-info">
+					<text class="name">{{orderInfo.name}}</text>
+					<text class="phone">{{orderInfo.mobile}}</text>
+				</view>
+				<view class="address-detail">
+					{{orderInfo.province}}{{orderInfo.city}}{{orderInfo.area}}{{orderInfo.address}}
+				</view>
+			</view>
+		</view>
+
+		<!-- 商品信息 -->
+		<view class="goods-section">
+			<view class="goods-title">商品信息</view>
+			<view class="goods-item" v-for="(item, index) in orderDetails" :key="index">
+				<image :src="item.productImage" mode="aspectFill" class="goods-img"></image>
+				<view class="goods-info">
+					<view class="goods-name">{{item.productName}}</view>
+					<view class="goods-spec">{{item.skuName}}</view>
+					<view class="price-num">
+						<text class="price">¥{{item.unitPrice}}</text>
+						<text class="num">x{{item.quantity}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 支付方式 -->
+		<view class="pay-section">
+			<view class="pay-title">支付方式</view>
+			<view class="pay-method">
+				<view class="method-item">
+					<image src="/static/images/wxpay.png" mode="aspectFit" class="pay-icon"></image>
+					<text>微信支付</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 订单金额信息 -->
+		<view class="amount-section">
+			<view class="amount-item">
+				<text>商品总价</text>
+				<text>¥{{orderInfo.paymentPrice}}</text>
+			</view>
+			<view class="amount-item total">
+				<text>实付金额</text>
+				<text class="total-price">¥{{orderInfo.paymentPrice}}</text>
+			</view>
+		</view>
+
+		<!-- 底部支付按钮 -->
+		<view class="footer">
+			<view class="total-section">
+				<text>实付金额:</text>
+				<text class="total-price">¥{{orderInfo.paymentPrice}}</text>
+			</view>
+			<view class="pay-btn" @click="handlePay">
+				立即支付
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		getOrderPayDetail,
+		getWxPayParams
+	} from '@/config/api.js';
+
+	export default {
+		data() {
+			return {
+				orderId: '',
+				orderInfo: null,
+				orderDetails: []
+			}
+		},
+		onLoad(options) {
+			if (options.orderId) {
+				this.orderId = options.orderId;
+				this.getOrderDetail();
+			} else {
+				uni.showToast({
+					title: '订单参数错误',
+					icon: 'none'
+				});
+				setTimeout(() => {
+					uni.navigateBack();
+				}, 1500);
+			}
+		},
+		methods: {
+			// 获取订单详情
+			async getOrderDetail() {
+				try {
+					uni.showLoading({
+						title: '加载中...'
+					});
+					
+					const res = await getOrderPayDetail(this.orderId);
+					if (res.code === 200 && res.data) {
+						this.orderInfo = res.data.orderInfo;
+						this.orderDetails = res.data.orderDetails;
+						
+						// 如果订单状态不是待付款,提示并返回
+						if (this.orderInfo.status !== 1) {
+							uni.showToast({
+								title: '订单状态已改变',
+								icon: 'none'
+							});
+							setTimeout(() => {
+								uni.navigateBack();
+							}, 1500);
+						}
+					} else {
+						uni.showToast({
+							title: res.msg || '获取订单信息失败',
+							icon: 'none'
+						});
+					}
+				} catch (e) {
+					console.error('获取订单详情失败:', e);
+					uni.showToast({
+						title: '获取订单信息失败',
+						icon: 'none'
+					});
+				} finally {
+					uni.hideLoading();
+				}
+			},
+			
+			// 处理支付
+			async handlePay() {
+				if (!this.orderInfo) {
+					return uni.showToast({
+						title: '订单信息错误',
+						icon: 'none'
+					});
+				}
+				
+				try {
+					// 调用后端获取支付参数
+					const payRes = await getWxPayParams(this.orderId);
+					if (payRes.code !== 200 || !payRes.data) {
+						throw new Error(payRes.msg || '获取支付参数失败');
+					}
+					
+					// 调起微信支付
+					uni.requestPayment({
+						provider: 'wxpay',
+						timeStamp: payRes.data.timestamp,
+						nonceStr: payRes.data.noncestr,
+						package: payRes.data.package,
+						signType: payRes.data.signType,
+						paySign: payRes.data.sign,
+						success: (res) => {
+							uni.showToast({
+								title: '支付成功',
+								icon: 'success'
+							});
+							// 支付成功后跳转到订单列表
+							setTimeout(() => {
+								uni.redirectTo({
+									url: '/pages/order/list?status=2'
+								});
+							}, 1500);
+						},
+						fail: (err) => {
+							console.error('支付失败:', err);
+							uni.showToast({
+								title: '支付失败',
+								icon: 'none'
+							});
+						}
+					});
+				} catch (e) {
+					console.error('支付异常:', e);
+					uni.showToast({
+						title: e.message || '支付失败',
+						icon: 'none'
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.payment-container {
+		min-height: 100vh;
+		background-color: #f8f8f8;
+		padding-bottom: 120rpx;
+	}
+	
+	.address-section {
+		background-color: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		
+		.address-content {
+			.contact-info {
+				margin-bottom: 10rpx;
+				
+				.name {
+					margin-right: 20rpx;
+					font-weight: bold;
+				}
+				
+				.phone {
+					color: #666;
+				}
+			}
+			
+			.address-detail {
+				color: #333;
+				font-size: 28rpx;
+			}
+		}
+	}
+	
+	.goods-section {
+		background-color: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		
+		.goods-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			margin-bottom: 20rpx;
+		}
+		
+		.goods-item {
+			display: flex;
+			margin-bottom: 20rpx;
+			
+			.goods-img {
+				width: 160rpx;
+				height: 160rpx;
+				margin-right: 20rpx;
+				border-radius: 8rpx;
+			}
+			
+			.goods-info {
+				flex: 1;
+				
+				.goods-name {
+					font-size: 28rpx;
+					margin-bottom: 10rpx;
+				}
+				
+				.goods-spec {
+					font-size: 24rpx;
+					color: #666;
+					margin-bottom: 10rpx;
+				}
+				
+				.price-num {
+					display: flex;
+					justify-content: space-between;
+					
+					.price {
+						color: #ff4d4f;
+						font-weight: bold;
+					}
+					
+					.num {
+						color: #666;
+					}
+				}
+			}
+		}
+	}
+	
+	.pay-section {
+		background-color: #fff;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		
+		.pay-title {
+			font-size: 32rpx;
+			font-weight: bold;
+			margin-bottom: 20rpx;
+		}
+		
+		.method-item {
+			display: flex;
+			align-items: center;
+			
+			.pay-icon {
+				width: 40rpx;
+				height: 40rpx;
+				margin-right: 20rpx;
+			}
+		}
+	}
+	
+	.amount-section {
+		background-color: #fff;
+		padding: 30rpx;
+		
+		.amount-item {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 20rpx;
+			font-size: 28rpx;
+			
+			&.total {
+				margin-top: 20rpx;
+				padding-top: 20rpx;
+				border-top: 1px solid #eee;
+				
+				.total-price {
+					color: #ff4d4f;
+					font-weight: bold;
+					font-size: 32rpx;
+				}
+			}
+		}
+	}
+	
+	.footer {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 100rpx;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		padding: 0 30rpx;
+		box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
+		
+		.total-section {
+			flex: 1;
+			
+			.total-price {
+				color: #ff4d4f;
+				font-weight: bold;
+				font-size: 36rpx;
+			}
+		}
+		
+		.pay-btn {
+			width: 240rpx;
+			height: 80rpx;
+			background-color: #ff4d4f;
+			color: #fff;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			border-radius: 40rpx;
+			font-size: 32rpx;
+		}
+	}
+</style> 

+ 217 - 0
pages/order/score.vue

@@ -0,0 +1,217 @@
+<template>
+	<view>
+		<!-- 表单区域 -->
+		<view class="form-container region mar-t-10">
+			<view class="form-item">
+				<view class="form-label">评分</view>
+				<uni-rate size="30" v-model="score.score" />
+			</view>
+
+			<view class="form-item">
+				<view class="form-label">评价内容</view>
+				<view class="textarea-container">
+					<textarea style="width: 100%;" v-model="score.content" placeholder="请详细描述申请说明(选填)"
+						class="form-textarea" :maxlength="maxLength"></textarea>
+					<view class="char-count">
+						您还可以输入{{ remainingChars }} 字
+					</view>
+				</view>
+			</view>
+
+			<!-- 上传凭证 -->
+			<view class="form-item">
+				<view class="form-label">上传图片/视频(可选)</view>
+				<view class=" flex-items">
+					<view>
+						<uni-file-picker @delete="delFile" v-model="imageValue" fileMediatype="image" mode="grid"
+							@select="select">
+							<view class="videoUpload flex-items-plus">
+								<view class="text-align">
+									<uni-icons type="image" size="30"></uni-icons>
+									<view>
+										添加图片
+									</view>
+								</view>
+							</view>
+						</uni-file-picker>
+					</view>
+
+					<view>
+						<uni-file-picker @delete="delFile" v-model="imageValue" fileMediatype="video" mode="grid"
+							@select="select">
+							<view class="videoUpload flex-items-plus">
+								<view class="text-align">
+									<uni-icons type="videocam" size="30"></uni-icons>
+									<view>
+										添加短视频
+									</view>
+								</view>
+							</view>
+						</uni-file-picker>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 提交按钮 -->
+		<button class="submit-button" @click="submitForm('customForm')">提交评价</button>
+	</view>
+</template>
+
+<script>
+	import {
+		UPLOAD_URL
+	} from '../../common/config.js';
+	import {
+		saveScore
+	} from '../../config/api.js';
+
+	export default {
+
+		data() {
+			return {
+				score: {
+					imgList: [],
+					score: 5,
+					content: ""
+				},
+				token: '',
+				imageValue: [],
+				fileList: [],
+				images: [],
+				orderId: "",
+				maxLength: 200, // 最大字数
+			};
+		},
+		computed: {
+			// 计算剩余字数
+			remainingChars() {
+				return this.maxLength - this.score.content.length;
+			}
+		},
+		onLoad(op) {
+			this.orderId = op.orderId;
+			this.token = uni.getStorageSync('access_token');
+		},
+		methods: {
+			delFile(e) {
+				this.score.imgList.splice(e.index, 1); // 删除对应 index 的附件
+			},
+			// 获取选择的文件
+			async select(e) {
+				console.log(e)
+				let list = [].concat(e.tempFiles);
+				// for (let i = 0; i < list.length; i++) {
+				// 	const result = await this.uploadFilePromise(list[i].url);
+				// 	this.score.imgList.push(result.data.link);
+				// }
+			},
+			uploadFilePromise(url) {
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: UPLOAD_URL,
+						filePath: url,
+						header: {
+							"Blade-Auth": this.token
+						},
+						name: 'file',
+						formData: {
+							user: 'test'
+						},
+						success: (res) => {
+							resolve(JSON.parse(res.data));
+						}
+					});
+				});
+			},
+			// 提交表单
+			submitForm() {
+				this.score.images = this.score.imgList.join(',');
+				this.score.orderId = this.orderId
+				saveScore(this.score).then((res) => {
+					if (res.code == 200) {
+						uni.showToast({
+							title: '评价成功',
+							icon: 'success'
+						});
+						setTimeout(() => {
+							uni.navigateBack({
+								delta: 1
+							})
+						}, 1000)
+					}
+				})
+
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.page-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		text-align: center;
+		padding: 20rpx 0;
+		color: #333;
+	}
+
+	.form-container {
+		padding: 20rpx;
+	}
+
+	.form-item {
+
+		margin-bottom: 20rpx;
+	}
+
+	.form-label {
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 10rpx;
+	}
+
+	.textarea-container {
+		position: relative;
+	}
+
+	.form-textarea {
+		border: 1px solid #ccc;
+		border-radius: 5rpx;
+		padding: 10rpx;
+		font-size: 28rpx;
+		height: 150rpx;
+		resize: none;
+	}
+
+	.char-count {
+		position: absolute;
+		bottom: 10rpx;
+		right: 10rpx;
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	.upload-container {
+		margin-top: 10rpx;
+	}
+
+	.submit-button {
+		background-color: #F95B5B;
+		color: #fff;
+		font-size: 32rpx;
+		padding: 5rpx;
+		border-radius: 10rpx;
+		margin-left: 30rpx;
+		margin-right: 30rpx;
+		text-align: center;
+		margin-top: 30rpx;
+	}
+
+	.videoUpload {
+		border: 1rpx solid #999;
+
+		height: 200rpx;
+		width: 200rpx;
+	}
+</style>

+ 166 - 0
pages/order/search.vue

@@ -0,0 +1,166 @@
+<template>
+	<view :style="{ paddingTop: statusBarHeight + 'px' }">
+		<view class="flex-items pad-t-20">
+			<view class="mar-r-20">
+				<uni-icons @tap="back" type="left" color="#545454" size="30"></uni-icons>
+			</view>
+			<view class="search-box">
+				<input confirm-type="search" class="search-input" focus type="text" placeholder="请输入搜索内容"
+					v-model="historySearch.name" />
+				<uni-icons @tap="clear" v-if="historySearch.name" type="closeempty" color="#545454" size="20"
+					class="mar-r-20"></uni-icons>
+				<button class="search-button" @click="onSearchClick">搜索</button>
+			</view>
+		</view>
+		<view class="flex-items-between pad-32">
+			<view class="font28 font-bold">
+				历史搜索
+			</view>
+			<uni-icons @tap="delSearch" type="trash" size="26"></uni-icons>
+		</view>
+		<view class="pad-lr-32">
+			<view class="search-container">
+				<view v-for="(item, index) in historySearchList" :key="index" class="search-item">
+					<view class="searchBg mar-b-16" @tap="onHistorySearch(item.name)">
+						{{ item.name }}
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		delHistorySearch,
+		submitHistorySearch,
+		getHistorySearchList
+	} from '../../config/api.js';
+	import {
+		MAIN_COLOR
+	} from '../../common/config.js'
+
+	export default {
+		data() {
+			return {
+				bgColor: 'transparent',
+				statusBarHeight: 0, // 状态栏高度
+				historySearch: {
+					name: "",
+					type: ""
+				},
+				historySearchList: [],
+			}
+		},
+		onLoad(op) {
+			console.log(op.searchKey)
+			this.historySearch.type = op.type
+			if (op.searchKey) {
+				console.log(op.searchKey)
+				this.historySearch.name = op.searchKey
+			}
+			// 获取设备信息,包括状态栏高度
+			const systemInfo = uni.getSystemInfoSync();
+			this.statusBarHeight = systemInfo.statusBarHeight;
+			this.getSearch();
+		},
+		methods: {
+			clear() {
+				this.historySearch.name = ""
+			},
+			onHistorySearch(name) {
+				this.historySearch.name = name
+				if (this.historySearch.type == 2) {
+					this.$route('/pages/order/list?serachKey=' + this.historySearch.name)
+				}
+				if (this.historySearch.type == 1) {
+					this.$route('/pages/shop/product-type-list?serachKey=' + this.historySearch.name)
+				}
+			},
+			back() {
+				uni.navigateBack({
+					delta: 1
+				})
+			},
+			getSearch() {
+				getHistorySearchList({
+					type: this.historySearch.type
+				}).then((res) => {
+					this.historySearchList = res.data
+				})
+			},
+			delSearch() {
+				delHistorySearch().then((res) => {
+					this.getSearch()
+				})
+			},
+			onSearchClick() {
+				console.log(this.historySearch.type)
+				// 执行搜索操作
+				submitHistorySearch(this.historySearch)
+				if (this.historySearch.type == 2) {
+					this.$route('/pages/order/list?serachKey=' + this.historySearch.name)
+				}
+				if (this.historySearch.type == 1) {
+					this.$route('/pages/shop/product-type-list?serachKey=' + this.historySearch.name)
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	.search-container {
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.search-item {
+		margin-right: 10rpx;
+		margin-bottom: 10rpx;
+	}
+
+	.searchBg {
+		background-color: #d8d8d8;
+		padding: 8rpx 12rpx;
+		border-radius: 10rpx;
+		font-size: 14px;
+		color: #333;
+	}
+
+	.search-box {
+		display: flex;
+		align-items: center;
+		border-radius: 20rpx;
+		background-color: #f0f0f0;
+		padding: 10rpx 20rpx;
+		width: 100%;
+		/* Ensure it takes full width */
+	}
+
+	.search-input {
+		flex: 1;
+		border: none;
+		outline: none;
+		font-size: 14px;
+		color: #999;
+		background-color: #f0f0f0;
+		border-radius: 20rpx;
+		padding: 8rpx 12rpx;
+		width: 100%;
+	}
+
+	.search-button {
+		background-color: #f27c22;
+		/* 橘色背景 */
+		color: white;
+		border: none;
+		padding: 0rpx 30rpx 0rpx 30rpx;
+		border-radius: 20rpx;
+		margin-left: 10rpx;
+		font-size: 14px;
+		cursor: pointer;
+		white-space: nowrap;
+		/* Prevent text overflow */
+	}
+</style>

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott