Browse Source

Merge branch 'master' of http://gitlab.frkj.cc/php/ZhiHuiJiaoWu_Admin

# Conflicts:
#	admin/components.d.ts
master
于宏哲PHP 1 year ago
parent
commit
162fd60932
  1. 7
      admin/src/addon/zhjw/api/hello_world.ts
  2. 1
      admin/src/addon/zhjw/lang/zh-cn/hello_world.index.json
  3. 17
      admin/src/addon/zhjw/views/hello_world/index.vue
  4. 36
      niucloud/addon/zhjw/Addon.php
  5. 7
      niucloud/addon/zhjw/admin/api/hello_world.ts
  6. 1
      niucloud/addon/zhjw/admin/lang/zh-cn/hello_world.index.json
  7. 17
      niucloud/addon/zhjw/admin/views/hello_world/index.vue
  8. 29
      niucloud/addon/zhjw/app/adminapi/controller/hello_world/Index.php
  9. 30
      niucloud/addon/zhjw/app/adminapi/route/route.php
  10. 27
      niucloud/addon/zhjw/app/api/controller/hello_world/Index.php
  11. 36
      niucloud/addon/zhjw/app/api/route/route.php
  12. 33
      niucloud/addon/zhjw/app/dict/menu/admin.php
  13. 12
      niucloud/addon/zhjw/app/event.php
  14. 4
      niucloud/addon/zhjw/app/lang/en/api.php
  15. 4
      niucloud/addon/zhjw/app/lang/en/dict.php
  16. 4
      niucloud/addon/zhjw/app/lang/en/validate.php
  17. 4
      niucloud/addon/zhjw/app/lang/zh-cn/api.php
  18. 4
      niucloud/addon/zhjw/app/lang/zh-cn/dict.php
  19. 4
      niucloud/addon/zhjw/app/lang/zh-cn/validate.php
  20. 11
      niucloud/addon/zhjw/info.json
  21. 19
      niucloud/addon/zhjw/package/uni-app-pages.php
  22. BIN
      niucloud/addon/zhjw/resource/cover.png
  23. BIN
      niucloud/addon/zhjw/resource/icon.png
  24. 0
      niucloud/addon/zhjw/sql/install.sql
  25. 0
      niucloud/addon/zhjw/sql/uninstall.sql
  26. 8
      niucloud/addon/zhjw/uni-app/api/hello_world.ts
  27. 2
      niucloud/addon/zhjw/uni-app/locale/en.json
  28. 2
      niucloud/addon/zhjw/uni-app/locale/zh-Hans.json
  29. 2
      niucloud/addon/zhjw/uni-app/locale/zh-Hans/pages.hello_world.index.json
  30. 18
      niucloud/addon/zhjw/uni-app/pages/hello_world/index.vue
  31. 8
      niucloud/addon/zhjw/web/api/hello_world.ts
  32. 1
      niucloud/addon/zhjw/web/lang/zh-cn/hello_world.index.json
  33. 9
      niucloud/addon/zhjw/web/lang/zh-cn/pages.json
  34. 15
      niucloud/addon/zhjw/web/pages/hello_world/index.vue
  35. 6
      niucloud/addon/zhjw/web/pages/routes.ts
  36. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_01.png
  37. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_02.png
  38. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_03.png
  39. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_04.png
  40. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_05.png
  41. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_06.png
  42. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_07.png
  43. BIN
      niucloud/public/addon/shop/attachment/200x200_active_cube_08.png
  44. BIN
      niucloud/public/addon/shop/attachment/200x200_image_ads_01.png
  45. BIN
      niucloud/public/addon/shop/attachment/200x200_nav_sow_community.png
  46. BIN
      niucloud/public/addon/zhjw/cover.png
  47. BIN
      niucloud/public/addon/zhjw/icon.png
  48. 4
      niucloud/runtime/cache/01/c8e2ef1c2c504601d752e81c5f8f57.php
  49. 4
      niucloud/runtime/cache/04/a77b15025b2c34a79ecfe786681739.php
  50. 4
      niucloud/runtime/cache/2c/e25dcb326df717900650d384ad7cf0.php
  51. 4
      niucloud/runtime/cache/52/d44be2ad665ecbf618377422b91c6d.php
  52. 4
      niucloud/runtime/cache/59/6906eba8db5ea13aeb60bbd2e93269.php
  53. 2
      niucloud/runtime/cache/60/20f2824b87d39008be5a28cef5ef03.php
  54. 4
      niucloud/runtime/cache/7e/a633578462c9bc9f474ad990ba553d.php
  55. 2
      niucloud/runtime/cache/8d/fdacbfbb7ae58ba988054e909e2307.php
  56. 4
      niucloud/runtime/cache/96/42cea4fc1e386bb972a5e5dec82fda.php
  57. 2
      niucloud/runtime/cache/9f/d2f37875d1da637002182ec566f162.php
  58. 4
      niucloud/runtime/cache/c0/2ea470914e2a1b4422c74fb53b3fb7.php
  59. 4
      niucloud/runtime/cache/ed/b6cf0da7ae6bc4e108cde29a092460.php
  60. 3
      uni-app/.babelrc
  61. 19
      uni-app/.env.development
  62. 19
      uni-app/.env.production
  63. 21
      uni-app/.gitignore
  64. 20
      uni-app/index.html
  65. 21880
      uni-app/package-lock.json
  66. 86
      uni-app/package.json
  67. 120
      uni-app/publish.cjs
  68. 205
      uni-app/src/App.vue
  69. 59
      uni-app/src/addon/components/diy-form-detail/index.vue
  70. 191
      uni-app/src/addon/components/diy-form/index.vue
  71. 29
      uni-app/src/addon/components/diy/group/index.scss
  72. 212
      uni-app/src/addon/components/diy/group/index.vue
  73. 185
      uni-app/src/addon/components/diy/group/useDiyGroup.ts
  74. 59
      uni-app/src/addon/shop/api/cart.ts
  75. 8
      uni-app/src/addon/shop/api/config.ts
  76. 65
      uni-app/src/addon/shop/api/coupon.ts
  77. 25
      uni-app/src/addon/shop/api/discount.ts
  78. 29
      uni-app/src/addon/shop/api/evaluate.ts
  79. 107
      uni-app/src/addon/shop/api/goods.ts
  80. 16
      uni-app/src/addon/shop/api/newcomer.ts
  81. 86
      uni-app/src/addon/shop/api/order.ts
  82. 47
      uni-app/src/addon/shop/api/point.ts
  83. 29
      uni-app/src/addon/shop/api/rank.ts
  84. 71
      uni-app/src/addon/shop/api/refund.ts
  85. 8
      uni-app/src/addon/shop/api/shop.ts
  86. 382
      uni-app/src/addon/shop/components/diy/goods-coupon/index.vue
  87. 434
      uni-app/src/addon/shop/components/diy/goods-list/index.vue
  88. 340
      uni-app/src/addon/shop/components/diy/many-goods-list/index.vue
  89. 234
      uni-app/src/addon/shop/components/diy/shop-exchange-goods/index.vue
  90. 125
      uni-app/src/addon/shop/components/diy/shop-exchange-info/index.vue
  91. 190
      uni-app/src/addon/shop/components/diy/shop-goods-ranking/index.vue
  92. 203
      uni-app/src/addon/shop/components/diy/shop-goods-recommend/index.vue
  93. 262
      uni-app/src/addon/shop/components/diy/shop-member-info/index.vue
  94. 476
      uni-app/src/addon/shop/components/diy/shop-newcomer/index.vue
  95. 156
      uni-app/src/addon/shop/components/diy/shop-order-info/index.vue
  96. 102
      uni-app/src/addon/shop/components/diy/shop-search/index.vue
  97. 255
      uni-app/src/addon/shop/components/diy/single-recommend/index.vue
  98. 141
      uni-app/src/addon/shop/components/ns-goods-manjian/ns-goods-manjian.vue
  99. 69
      uni-app/src/addon/shop/components/ns-goods-recommend/ns-goods-recommend.vue
  100. 459
      uni-app/src/addon/shop/components/ns-goods-sku/ns-goods-sku.vue

7
admin/src/addon/zhjw/api/hello_world.ts

@ -0,0 +1,7 @@
import request from '@/utils/request'
/***************************************************** hello world ****************************************************/
export function getHelloWorld() {
return request.get(`zhjw/hello_world`)
}

1
admin/src/addon/zhjw/lang/zh-cn/hello_world.index.json

@ -0,0 +1 @@
{}

17
admin/src/addon/zhjw/views/hello_world/index.vue

@ -0,0 +1,17 @@
<template>
<span class="text-[20px]">{{hello_world_text}}</span>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { getHelloWorld } from '@/addon/zhjw/api/hello_world'
const hello_world_text = ref('');
const getHelloWorldInfo = async () => {
hello_world_text.value = await (await getHelloWorld()).data
}
getHelloWorldInfo()
</script>
<style lang="scss" scoped>
</style>

36
niucloud/addon/zhjw/Addon.php

@ -0,0 +1,36 @@
<?php
namespace addon\zhjw;
/**
* 插件安装之后单独的插件方法
*/
class Addon
{
/**
* 插件安装执行
*/
public function install()
{
return true;
}
/**
* 插件卸载执行
*/
public function uninstall()
{
return true;
}
/**
* 插件升级执行
*/
public function upgrade()
{
return true;
}
}

7
niucloud/addon/zhjw/admin/api/hello_world.ts

@ -0,0 +1,7 @@
import request from '@/utils/request'
/***************************************************** hello world ****************************************************/
export function getHelloWorld() {
return request.get(`zhjw/hello_world`)
}

1
niucloud/addon/zhjw/admin/lang/zh-cn/hello_world.index.json

@ -0,0 +1 @@
{}

17
niucloud/addon/zhjw/admin/views/hello_world/index.vue

@ -0,0 +1,17 @@
<template>
<span class="text-[20px]">{{hello_world_text}}</span>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { getHelloWorld } from '@/addon/zhjw/api/hello_world'
const hello_world_text = ref('');
const getHelloWorldInfo = async () => {
hello_world_text.value = await (await getHelloWorld()).data
}
getHelloWorldInfo()
</script>
<style lang="scss" scoped>
</style>

29
niucloud/addon/zhjw/app/adminapi/controller/hello_world/Index.php

@ -0,0 +1,29 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\adminapi\controller\hello_world;
use core\base\BaseAdminController;
use think\Response;
class Index extends BaseAdminController
{
/**
* Hello World
* @return Response
*/
public function index()
{
return success('SUCCESS', 'Hello World');
}
}

30
niucloud/addon/zhjw/app/adminapi/route/route.php

@ -0,0 +1,30 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use think\facade\Route;
use app\adminapi\middleware\AdminCheckRole;
use app\adminapi\middleware\AdminCheckToken;
use app\adminapi\middleware\AdminLog;
/**
* 智慧教务系统
*/
Route::group('zhjw', function () {
/***************************************************** hello world ****************************************************/
Route::get('hello_world', 'addon\zhjw\app\adminapi\controller\hello_world\Index@index');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,
AdminLog::class
]);

27
niucloud/addon/zhjw/app/api/controller/hello_world/Index.php

@ -0,0 +1,27 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\zhjw\app\api\controller\hello_world;
use core\base\BaseApiController;
use think\Response;
class Index extends BaseApiController
{
/**
* Hello World
* @return Response
*/
public function index()
{
return success('SUCCESS', 'Hello World');
}
}

36
niucloud/addon/zhjw/app/api/route/route.php

@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use app\api\middleware\ApiCheckToken;
use app\api\middleware\ApiLog;
use app\api\middleware\ApiChannel;
use think\facade\Route;
/**
* 智慧教务系统
*/
Route::group('zhjw', function() {
/***************************************************** hello world ****************************************************/
Route::get('hello_world', 'addon\zhjw\app\api\controller\hello_world\Index@index');
})->middleware(ApiChannel::class)
->middleware(ApiCheckToken::class, false) //false表示不验证登录
->middleware(ApiLog::class);
Route::group('zhjw', function() {
})->middleware(ApiChannel::class)
->middleware(ApiCheckToken::class, true) //表示验证登录
->middleware(ApiLog::class);

33
niucloud/addon/zhjw/app/dict/menu/admin.php

@ -0,0 +1,33 @@
<?php
return [
[
'menu_name' => '智慧教务系统',
'menu_key' => 'zhjw',
'menu_type' => 0,
'icon' => '',
'api_url' => '',
'router_path' => '',
'view_path' => '',
'methods' => '',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '智慧教务系统',
'menu_key' => 'zhjw_hello_world',
'menu_type' => 1,
'icon' => '',
'api_url' => 'zhjw/hello_world',
'router_path' => 'zhjw/hello_world',
'view_path' => 'hello_world/index',
'methods' => 'get',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => []
],
]
]
];

12
niucloud/addon/zhjw/app/event.php

@ -0,0 +1,12 @@
<?php
return [
'bind' => [
],
'listen' => [
],
'subscribe' => [
],
];

4
niucloud/addon/zhjw/app/lang/en/api.php

@ -0,0 +1,4 @@
<?php
return [
];

4
niucloud/addon/zhjw/app/lang/en/dict.php

@ -0,0 +1,4 @@
<?php
return [
];

4
niucloud/addon/zhjw/app/lang/en/validate.php

@ -0,0 +1,4 @@
<?php
return [
];

4
niucloud/addon/zhjw/app/lang/zh-cn/api.php

@ -0,0 +1,4 @@
<?php
return [
];

4
niucloud/addon/zhjw/app/lang/zh-cn/dict.php

@ -0,0 +1,4 @@
<?php
return [
];

4
niucloud/addon/zhjw/app/lang/zh-cn/validate.php

@ -0,0 +1,4 @@
<?php
return [
];

11
niucloud/addon/zhjw/info.json

@ -0,0 +1,11 @@
{
"title": "智慧教务系统",
"desc": "智慧教务系统",
"key": "zhjw",
"version": "1.0.0",
"author": "836164388@qq.com",
"type": "app",
"support_app": "",
"compile":[],
"support_version": "1.5.1"
}

19
niucloud/addon/zhjw/package/uni-app-pages.php

@ -0,0 +1,19 @@
<?php
return [
'pages' => <<<EOT
// PAGE_BEGIN
// *********************************** {{addon_name}} ***********************************
{
"root": "addon/{{addon_name}}",
"pages": [
{
"path": "pages/hello_world/index",
"style": {
"navigationBarTitleText": "%{{addon_name}}.pages.hello_world.index%"
}
}
]
},
// PAGE_END
EOT
];

BIN
niucloud/addon/zhjw/resource/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
niucloud/addon/zhjw/resource/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

0
niucloud/addon/zhjw/sql/install.sql

0
niucloud/addon/zhjw/sql/uninstall.sql

8
niucloud/addon/zhjw/uni-app/api/hello_world.ts

@ -0,0 +1,8 @@
import request from '@/utils/request'
/***************************************************** hello world ****************************************************/
export function getHelloWorld() {
return request.get(`zhjw/hello_world`)
}

2
niucloud/addon/zhjw/uni-app/locale/en.json

@ -0,0 +1,2 @@
{}

2
niucloud/addon/zhjw/uni-app/locale/zh-Hans.json

@ -0,0 +1,2 @@
{}

2
niucloud/addon/zhjw/uni-app/locale/zh-Hans/pages.hello_world.index.json

@ -0,0 +1,2 @@
{}

18
niucloud/addon/zhjw/uni-app/pages/hello_world/index.vue

@ -0,0 +1,18 @@
<template>
<text class="text-[20px]">{{helloWorld}}</text>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getHelloWorld } from '@/addon/zhjw/api/hello_world'
import { onLoad } from '@dcloudio/uni-app'
let helloWorld = ref('');
onLoad(() => {
getHelloWorld().then((res) => {
helloWorld.value = res.data
})
})
</script>
<style lang="scss" scoped>
</style>

8
niucloud/addon/zhjw/web/api/hello_world.ts

@ -0,0 +1,8 @@
/**
* hello world
*/
export function getHelloWorld() {
return request.get('zhjw/hello_world')
}

1
niucloud/addon/zhjw/web/lang/zh-cn/hello_world.index.json

@ -0,0 +1 @@
{}

9
niucloud/addon/zhjw/web/lang/zh-cn/pages.json

@ -0,0 +1,9 @@
{
"pages": {
"zhjw": {
"hello_world": {
"index": "hello_world"
}
}
}
}

15
niucloud/addon/zhjw/web/pages/hello_world/index.vue

@ -0,0 +1,15 @@
<template>
<span class="text-[24px]">{{hello_world_text}}</span>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { getHelloWorld } from '@/addon/zhjw/api/hello_world'
const hello_world_text = ref('');
getHelloWorld().then(res => {
hello_world_text.value = res.data;
})
</script>
<style lang="scss" scoped></style>

6
niucloud/addon/zhjw/web/pages/routes.ts

@ -0,0 +1,6 @@
export default [
{
path: "/zhjw/hello_world/index",
component: () => import('~/addon/zhjw/pages/hello_world/index.vue')
}
]

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_03.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_04.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_05.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_06.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_07.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_active_cube_08.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_image_ads_01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
niucloud/public/addon/shop/attachment/200x200_nav_sow_community.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
niucloud/public/addon/zhjw/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
niucloud/public/addon/zhjw/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

4
niucloud/runtime/cache/01/c8e2ef1c2c504601d752e81c5f8f57.php

@ -0,0 +1,4 @@
<?php
//000000000000
exit();?>
-1

4
niucloud/runtime/cache/04/a77b15025b2c34a79ecfe786681739.php

@ -0,0 +1,4 @@
<?php
//000000000000
exit();?>
a:6:{s:2:"id";i:2;s:10:"config_key";s:23:"START_UP_PAGE_DIY_INDEX";s:5:"value";a:6:{s:4:"type";s:9:"DIY_INDEX";s:4:"name";s:10:"SHOP_INDEX";s:6:"parent";s:9:"SHOP_LINK";s:4:"page";s:23:"/addon/shop/pages/index";s:5:"title";s:24:"dict_diy.shop_link_index";s:6:"action";s:8:"decorate";}s:6:"status";i:1;s:11:"create_time";s:19:"2025-03-04 09:13:36";s:11:"update_time";s:19:"2025-03-04 09:13:36";}

4
niucloud/runtime/cache/2c/e25dcb326df717900650d384ad7cf0.php

@ -1,4 +0,0 @@
<?php
//000000000000
exit();?>
a:3:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8d\4b55ce338f9b3a100a33ac8e8243e8.php";i:1;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\20\336a007c8134208722cf4e14270f07.php";i:2;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\32\0a87fb2f5a7e9725055a2c99bd1e51.php";}

4
niucloud/runtime/cache/52/d44be2ad665ecbf618377422b91c6d.php

@ -0,0 +1,4 @@
<?php
//000000000300
exit();?>
a:2:{s:9:"secretKey";s:16:"hfk4kwq8r6p7ucrq";s:5:"point";O:27:"Fastknife\Domain\Vo\PointVo":2:{s:1:"x";i:179;s:1:"y";i:5;}}

4
niucloud/runtime/cache/59/6906eba8db5ea13aeb60bbd2e93269.php

@ -0,0 +1,4 @@
<?php
//000000000300
exit();?>
a:2:{s:9:"secretKey";s:16:"tzex4hnus36vtgyn";s:5:"point";O:27:"Fastknife\Domain\Vo\PointVo":2:{s:1:"x";i:181;s:1:"y";i:5;}}

2
niucloud/runtime/cache/60/20f2824b87d39008be5a28cef5ef03.php

@ -1,4 +1,4 @@
<?php <?php
//000000000000 //000000000000
exit();?> exit();?>
a:11:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8d\edc20f770c6500c52ce13d6f306b10.php";i:1;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\f0\8f735f0ec7f9eccc3e70aa516d941d.php";i:2;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\47\0f6e29f334d3244ee0749eb9b1a151.php";i:3;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\bd\359a5364051f6c84d65c787bf23f9a.php";i:4;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\f4\073ed8013d62bb2e0f768418925e4d.php";i:5;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\c8\a5c3cd9f43d503a892ec0eaeb9ab08.php";i:6;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\a0\81b1d0c6234fbc717b33719be823a2.php";i:7;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\95\228e6be1c6483f594d6d736cba9335.php";i:8;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8f\7fdd2caa2302a943c6b21d4b077526.php";i:9;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\21\6e30a16639b283b1efa6b98838867b.php";i:10;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\85\c6bb2855c5978a0ab5483ed7646a3b.php";} a:16:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8d\edc20f770c6500c52ce13d6f306b10.php";i:1;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\f0\8f735f0ec7f9eccc3e70aa516d941d.php";i:2;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\47\0f6e29f334d3244ee0749eb9b1a151.php";i:3;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\bd\359a5364051f6c84d65c787bf23f9a.php";i:4;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\f4\073ed8013d62bb2e0f768418925e4d.php";i:5;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\c8\a5c3cd9f43d503a892ec0eaeb9ab08.php";i:6;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\a0\81b1d0c6234fbc717b33719be823a2.php";i:7;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\95\228e6be1c6483f594d6d736cba9335.php";i:8;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8f\7fdd2caa2302a943c6b21d4b077526.php";i:9;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\21\6e30a16639b283b1efa6b98838867b.php";i:10;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\85\c6bb2855c5978a0ab5483ed7646a3b.php";i:11;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\01\c8e2ef1c2c504601d752e81c5f8f57.php";i:12;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\04\a77b15025b2c34a79ecfe786681739.php";i:13;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\ed\b6cf0da7ae6bc4e108cde29a092460.php";i:14;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\c0\2ea470914e2a1b4422c74fb53b3fb7.php";i:15;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\96\42cea4fc1e386bb972a5e5dec82fda.php";}

4
niucloud/runtime/cache/7e/a633578462c9bc9f474ad990ba553d.php

@ -1,4 +0,0 @@
<?php
//000000000000
exit();?>
a:1:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\11\96761b4a84f0e988872166216d02cd.php";}

2
niucloud/runtime/cache/8d/fdacbfbb7ae58ba988054e909e2307.php

@ -1,4 +1,4 @@
<?php <?php
//000000000000 //000000000000
exit();?> exit();?>
a:3:{i:0;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwMDc3MDIsIm5iZiI6MTc0MTAwNzcwMiwiZXhwIjoxNzQxNjEyNTAyLCJqdGkiOiIxX2FkbWluIn0.ARwVjIYCdNUSwEmmm1KhrY1zKEWhRME0j26HM_GUNc4";i:1;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwNTA4MTYsIm5iZiI6MTc0MTA1MDgxNiwiZXhwIjoxNzQxNjU1NjE2LCJqdGkiOiIxX2FkbWluIn0.aZLpv4RBIlBCbnnqV9oXk9kC5AgdKHTUYiS3CCBSAqw";i:2;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwNTE0MTksIm5iZiI6MTc0MTA1MTQxOSwiZXhwIjoxNzQxNjU2MjE5LCJqdGkiOiIxX2FkbWluIn0.znLMCEfxdiRUIwiCBFah2d4cMlkJAazGbfAPU2C4bfo";} a:4:{i:0;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwMDc3MDIsIm5iZiI6MTc0MTAwNzcwMiwiZXhwIjoxNzQxNjEyNTAyLCJqdGkiOiIxX2FkbWluIn0.ARwVjIYCdNUSwEmmm1KhrY1zKEWhRME0j26HM_GUNc4";i:1;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwNTA4MTYsIm5iZiI6MTc0MTA1MDgxNiwiZXhwIjoxNzQxNjU1NjE2LCJqdGkiOiIxX2FkbWluIn0.aZLpv4RBIlBCbnnqV9oXk9kC5AgdKHTUYiS3CCBSAqw";i:2;s:264:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJzaG9wYWRtaW4uY2MiLCJhdWQiOiJzaG9wYWRtaW4uY2MiLCJpYXQiOjE3NDEwNTE0MTksIm5iZiI6MTc0MTA1MTQxOSwiZXhwIjoxNzQxNjU2MjE5LCJqdGkiOiIxX2FkbWluIn0.znLMCEfxdiRUIwiCBFah2d4cMlkJAazGbfAPU2C4bfo";i:3;s:251:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpc3MiOiJ6aGp3LmNjIiwiYXVkIjoiemhqdy5jYyIsImlhdCI6MTc0MTE1NjA1NSwibmJmIjoxNzQxMTU2MDU1LCJleHAiOjE3NDE3NjA4NTUsImp0aSI6IjFfYWRtaW4ifQ.Wt0Rfttva_ld-or-b7Ya5UI6TIJsTpu7ZyBIjXNJAsw";}

4
niucloud/runtime/cache/96/42cea4fc1e386bb972a5e5dec82fda.php

@ -0,0 +1,4 @@
<?php
//000000000000
exit();?>
-1

2
niucloud/runtime/cache/9f/d2f37875d1da637002182ec566f162.php

@ -1,4 +1,4 @@
<?php <?php
//000000000000 //000000000000
exit();?> exit();?>
a:1:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8d\fdacbfbb7ae58ba988054e909e2307.php";} a:2:{i:0;s:88:"D:\phpstudy_pro\WWW\niushop\niucloud\runtime\cache\8d\fdacbfbb7ae58ba988054e909e2307.php";i:1;s:121:"D:\DeveloperApp\phpstudy_pro\WWW\NiuCloud_ZhiHuiJiaoWu_Admin\niucloud\runtime\cache\8d\fdacbfbb7ae58ba988054e909e2307.php";}

4
niucloud/runtime/cache/c0/2ea470914e2a1b4422c74fb53b3fb7.php

@ -0,0 +1,4 @@
<?php
//000000000000
exit();?>
-1

4
niucloud/runtime/cache/ed/b6cf0da7ae6bc4e108cde29a092460.php

@ -0,0 +1,4 @@
<?php
//000000000000
exit();?>
-1

3
uni-app/.babelrc

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

19
uni-app/.env.development

@ -0,0 +1,19 @@
NODE_ENV = 'development'
# api请求地址
VITE_APP_BASE_URL=''
# 图片服务器地址
VITE_IMG_DOMAIN=''
# 本地存储时token的参数名
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken'
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中来源场景的参数名
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
# 应用版本
VITE_APP_VERSION='1.0.1'

19
uni-app/.env.production

@ -0,0 +1,19 @@
NODE_ENV = 'production'
# api请求地址
VITE_APP_BASE_URL=''
# 图片服务器地址
VITE_IMG_DOMAIN=''
# 本地存储时token的参数名
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken'
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中来源场景的参数名
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
# 应用版本
VITE_APP_VERSION='1.0.1'

21
uni-app/.gitignore

@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

20
uni-app/index.html

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="zh-cn">
<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="/src/main.js"></script>
</body>
</html>

21880
uni-app/package-lock.json

File diff suppressed because it is too large

86
uni-app/package.json

@ -0,0 +1,86 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "node publish.cjs mp-weixin dev",
"dev:niu-mp-weixin": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build && node publish.cjs h5 build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin && node publish.cjs mp-weixin build",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3080720230703001",
"@dcloudio/uni-app-plus": "3.0.0-3080720230703001",
"@dcloudio/uni-components": "3.0.0-3080720230703001",
"@dcloudio/uni-h5": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-alipay": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-baidu": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-jd": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-lark": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-qq": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3080720230703001",
"@dcloudio/uni-mp-weixin": "3.0.0-3080720230703001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3080720230703001",
"html2canvas": "^1.4.1",
"image-tools": "^1.4.0",
"lodash-es": "^4.17.21",
"pinia": "2.0.36",
"qrcode": "^1.5.1",
"qs": "6.7.0",
"sortablejs": "^1.15.0",
"uview-plus": "^3.1.29",
"vue": "^3.3.0",
"vue-i18n": "^9.2.2",
"weixin-js-sdk": "^1.6.5"
},
"devDependencies": {
"@dcasia/mini-program-tailwind-webpack-plugin": "^1.5.6",
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-3080720230703001",
"@dcloudio/uni-cli-shared": "3.0.0-3080720230703001",
"@dcloudio/uni-stacktracey": "3.0.0-3080720230703001",
"@dcloudio/vite-plugin-uni": "3.0.0-3080720230703001",
"@rollup/plugin-commonjs": "^24.0.1",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"@vue/tsconfig": "^0.1.3",
"sass": "^1.54.5",
"typescript": "^4.9.4",
"vite": "4.0.4",
"vite-plugin-windicss": "^1.8.10",
"vue-tsc": "^1.0.24",
"windicss": "^3.5.6"
}
}

120
uni-app/publish.cjs

@ -0,0 +1,120 @@
const fs = require('fs')
const { spawn } = require('child_process');
const path = require('path');
const main = () => {
const params = process.argv.slice(2) || []
const port = params[0] || ''
const mode = params[1] || ''
switch (port) {
case 'h5':
publish()
break;
case 'mp-weixin':
if (mode == 'build') {
handleWeappAddonComponents(mode)
handleWeappLanguage(mode)
} else if (mode == 'dev') {
listenWeappRunDev()
}
break;
}
}
const publish = () => {
const src = './dist/build/h5'
const dest = '../niucloud/public/wap'
solve()
// 目标目录不存在停止复制
try {
const dir = fs.readdirSync(dest)
} catch (e) {
return
}
// 删除目标目录下文件
fs.rm(dest, { recursive: true }, err => {
if(err) {
console.log(err)
return
}
fs.cp(src, dest, { recursive: true }, (err) => {
if (err) {
console.error(err)
}
})
})
}
const solve = () => {
const src = './dist/build/h5/assets'
const filemaps = fs.readdirSync(src)
filemaps.forEach(file => {
if (/^(index-)(\w{8})(.js)$/.test(file)) {
const path = `${src}/${file}`
let content = fs.readFileSync(path, 'utf-8')
const first = 'const match = location.href.match(/\\/wap\\/(\\d*)\\//);'
if (content.indexOf(first) == -1) {
content = first + content
const replace = 'router:{mode:"history",base: match ? `/wap/${match[1]}/` : "/wap/",assets:"assets",routerBase: match ? `/wap/${match[1]}/` : "/wap/"},darkmode'
content = content.replace(/router:{(.*?)},darkmode/s, replace)
fs.writeFileSync(path, content, 'utf8')
}
}
})
}
const handleWeappAddonComponents = (mode) => {
const src = `./dist/${mode}/mp-weixin/addon/components/diy/group/index.json`
try {
const data = JSON.parse(fs.readFileSync(src, 'utf8'));
data.componentPlaceholder = {};
Object.keys(data.usingComponents).map(key => {
data.componentPlaceholder[key] = "view";
})
fs.writeFileSync(src, JSON.stringify(data))
} catch (err) {
}
}
const handleWeappLanguage = (mode) => {
const src = `./dist/${mode}/mp-weixin/locale/language.js`
try {
let content = fs.readFileSync(src, 'utf8');
content = content.replace(/Promise\.resolve\(require\(("[^"]+")\)\)/g, 'require.async($1)')
fs.writeFileSync(src, content)
} catch (err) {
console.log(err)
}
}
const listenWeappRunDev = () => {
const devProcess = spawn('npm', ['run', 'dev:niu-mp-weixin'], {
stdio: ['pipe', 'pipe', 'pipe'],
shell: true
});
let serverReady = false;
// 监听 stdout 输出
devProcess.stdout.on('data', (data) => {
const message = data.toString();
console.log(message)
if (!serverReady && message.includes('DONE Build complete')) {
serverReady = true;
handleWeappAddonComponents('dev')
handleWeappLanguage('dev')
}
});
}
main()

205
uni-app/src/App.vue

@ -0,0 +1,205 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { launchInterceptor } from '@/utils/interceptor'
import { getToken, isWeixinBrowser, currRoute, deepClone, setThemeColor } from '@/utils/common'
import useMemberStore from '@/stores/member'
import useConfigStore from '@/stores/config'
import useSystemStore from '@/stores/system'
import { useLogin } from '@/hooks/useLogin'
import { useShare } from '@/hooks/useShare'
onLaunch((data: any) => {
//
launchInterceptor()
// #ifdef H5
uni.getSystemInfoSync().platform == 'ios' && (uni.setStorageSync('initUrl', location.href))
//
window.parent.postMessage(JSON.stringify({
type: 'appOnLaunch',
message: '初始化加载完成'
}), '*');
//
window.addEventListener('message', event => {
try {
let data = {
type: ''
};
if (typeof event.data == 'string') {
data = JSON.parse(event.data)
} else if (typeof event.data == 'object') {
data = event.data
}
if (data.type && data.type == 'appOnReady') {
window.parent.postMessage(JSON.stringify({
type: 'appOnReady',
message: '加载完成'
}), '*');
}
} catch (e) {
console.log('uni-app App.vue 接受数据错误', e)
}
}, false);
const { wechatInit } = useShare()
wechatInit()
// #endif
// #ifdef MP
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function (res) {
//
});
updateManager.onUpdateReady(function (res) {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(function (res) {
//
});
// #endif
//
useSystemStore().getInitFn(async() => {
const configStore = useConfigStore()
let loginConfig = uni.getStorageSync('login_config')
if (!loginConfig) {
loginConfig = deepClone(configStore.login)
}
let url: any = currRoute()
//
setThemeColor(url)
//
if ((['app/pages/auth/index', 'app/pages/auth/login', 'app/pages/auth/register', 'app/pages/auth/resetpwd'].indexOf(url) != -1) &&
(loginConfig.is_username || loginConfig.is_mobile || loginConfig.is_bind_mobile)) {
return false
}
//
if (getToken()) {
const memberStore: any = useMemberStore()
await memberStore.setToken(getToken(), () => {
if (!uni.getStorageSync('openid')) {
const memberInfo = useMemberStore().info
const login = useLogin()
// #ifdef MP-WEIXIN
if (memberInfo.mobile) uni.setStorageSync('wap_member_mobile', memberInfo.mobile) //
if (memberInfo && memberInfo.weapp_openid) {
uni.setStorageSync('openid', memberInfo.weapp_openid)
} else {
login.getAuthCode({ updateFlag: true }) // oppenid
}
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
if (memberInfo && memberInfo.wx_openid) {
uni.setStorageSync('openid', memberInfo.wx_openid)
} else {
if (data.query.code) {
// openid
login.updateOpenid(data.query.code, () => {
login.authLogin({ code: data.query.code })
})
} else {
if (loginConfig.is_force_access_user_info) {
//
login.getAuthCode({ scopes: 'snsapi_userinfo' })
} else {
//
login.getAuthCode({ scopes: 'snsapi_base' })
}
}
}
}
// #endif
}
//
if (uni.getStorageSync('isbindmobile')) {
uni.removeStorageSync('isbindmobile');
}
if (loginConfig.is_bind_mobile && !memberStore.info.mobile) {
//
uni.setStorageSync('isbindmobile', true)
}
})
}
if (!getToken()) {
// #ifdef MP
// 退
if (uni.getStorageSync('autoLoginLock')) return;
// #endif
const login = useLogin()
// #ifdef MP
//
if (loginConfig.is_auth_register) {
//
login.getAuthCode()
}
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
if (uni.getStorageSync('autoLoginLock') && !uni.getStorageSync('wechat_login_back')) return;
//
if (loginConfig.is_auth_register || uni.getStorageSync('wechat_login_back')) {
uni.removeStorageSync('wechat_login_back') //
if (data.query.code) {
login.authLogin({ code: data.query.code })
} else {
if (loginConfig.is_force_access_user_info) {
//
login.getAuthCode({ scopes: 'snsapi_userinfo' })
} else {
//
login.getAuthCode({ scopes: 'snsapi_base' })
}
}
}
}
// #endif
}
})
})
onShow(() => {
})
onHide(() => {
})
</script>
<style>
uni-page-head {
display: none !important;
}
</style>

59
uni-app/src/addon/components/diy-form-detail/index.vue

@ -0,0 +1,59 @@
<template>
<view :style="themeColor()">
<view v-show="!loading" class="diy-template-wrap">
<diy-group ref="diyGroupRef" :data="diyFormData" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue';
import diyGroup from '@/addon/components/diy/group/index.vue'
import { getFormRecord } from '@/app/api/diy_form';
const props = defineProps(['record_id','completeLayout']);
const emits = defineEmits(['callback'])
const loading = ref(true);
const diyFormData: any = reactive({
global: {},
value: []
})
onMounted(() => {
getFormRecord({
record_id: props.record_id
}).then((res: any) => {
diyFormData.global.completeLayout = props.completeLayout || 'style-1';
if (res.data.recordsFieldList) {
res.data.recordsFieldList.forEach((item: any) => {
let comp = {
id: item.field_key,
componentName: item.field_type,
pageStyle: '',
viewFormDetail: true, //
field: {
name: item.field_name,
value: item.handle_field_value,
required: item.field_required,
unique: item.field_unique,
privacyProtection: item.privacy_protection,
},
margin: {
top: 0,
bottom: 0,
both: 0
}
};
diyFormData.value.push(comp);
})
}
emits('callback', res.data.recordsFieldList)
loading.value = false;
}).catch(() => {
loading.value = false;
emits('callback', [])
})
})
</script>

191
uni-app/src/addon/components/diy-form/index.vue

@ -0,0 +1,191 @@
<template>
<view :style="themeColor()">
<!-- 自定义组件渲染 -->
<view v-show="requestData.status == 1 && !diy.getLoading()" class="diy-template-wrap">
<diy-group ref="diyGroupRef" :data="diyFormData" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted, watch } from 'vue';
import { useDiyForm } from '@/hooks/useDiyForm'
import { deepClone,getValidTime } from '@/utils/common'
import diyGroup from '@/addon/components/diy/group/index.vue'
const props = defineProps(['form_id', 'relate_id', 'storage_name', 'form_border']);
const diy = useDiyForm({
form_id: props.form_id,
needLogin: false //
})
const diyGroupRef = ref(null)
const requestData = computed(() => {
return diy.requestData;
})
const diyFormData: any = reactive({})
onMounted(() => {
diy.getData(() => {
diyFormData.status = diy.data.status;
if (diyFormData.status) {
diyFormData.title = diy.data.title;
diyFormData.global = diy.data.global;
if (diyFormData.global) {
diyFormData.global.topStatusBar.isShow = false; //
diyFormData.global.bottomTabBarSwitch = false; //
}
let value: any = [];
if(props.form_border == 'none'){
diyFormData.global.borderControl = false;
}
//
diy.data.value.forEach((item: any) => {
if (item.componentType == 'diy_form' && item.componentName != 'FormSubmit') {
value.push(item);
}
})
diyFormData.value = value;
diyFormData.componentRefs = null;
diyGroupRef.value?.refresh();
watchFormData();
}
})
})
const watchFormData = () => {
watch(
() => diyFormData.value,
(newValue, oldValue) => {
if (newValue) {
let formData: any = {
validTime: getValidTime(5), // 5
components: []
};
newValue.forEach((item: any) => {
//
if (item.componentType == 'diy_form' && item.componentName != 'FormSubmit') {
//
let field = deepClone(item.field);
//
delete field.remark; //
delete field.detailComponent; //
delete field.default; //
formData.components.push({
id: item.id,
componentName: item.componentName,
componentType: item.componentType,
componentTitle: item.componentTitle,
isHidden: item.isHidden,
field: field
})
}
})
if (formData.components.length) {
uni.setStorageSync('diyFormStorage_' + props.form_id, formData)
}
}
},
{ deep: true }
)
}
const verify = () => {
if(!diyFormData.status) return true;
if(!diyFormData.value) return true;
let allPass = true; //
let componentRefs = diyGroupRef.value.getFormRef().componentRefs;
//
for (let i = 0; i < diyFormData.value.length; i++) {
let item = diyFormData.value[i];
if (item.field.required || item.field.value) {
let refKey = `diy${ item.componentName }Ref`;
let isBreak = false;
if (componentRefs[refKey]) {
for (let k = 0; k < componentRefs[refKey].length; k++) {
let compRef = componentRefs[refKey][k];
let verify = compRef.verify(); //
if (verify && !verify.code) {
isBreak = true;
uni.showToast({
title: verify.message,
icon: 'none'
});
break;
}
}
if (isBreak) {
allPass = false;
break;
}
}
}
}
if (!allPass) return false;
const data = {
form_id: props.form_id,
value: uni.getStorageSync('diyFormStorage_' + props.form_id),
relate_id: props.relate_id || 0 // id
}
if (props.storage_name) {
uni.setStorageSync(props.storage_name, data)
}
return allPass;
}
//
const getData = ()=> {
return {
form_id: props.form_id,
value: diyFormData.value,
relate_id: props.relate_id || 0 // id
}
}
const clearStorage = (keys: any=[]) => {
uni.removeStorageSync('diyFormStorage_' + props.form_id)
if (props.storage_name) uni.removeStorageSync(props.storage_name)
if(keys) {
keys.forEach((key: any) => {
uni.removeStorageSync(key)
})
}
}
//
diy.onHide();
//
diy.onUnload();
//
// diy.onPageScroll()
defineExpose({
verify,
getData,
clearStorage
})
</script>
<style lang="scss">
.diy-template-wrap {
/* #ifdef MP */
.child-diy-template-wrap {
::v-deep .diy-group {
> .draggable-element.top-fixed-diy {
display: block !important;
}
}
}
/* #endif */
}
</style>

29
uni-app/src/addon/components/diy/group/index.scss

@ -0,0 +1,29 @@
.ignore-draggable-element, .draggable-element {
&.decorate {
&:hover:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 4rpx dotted $u-primary;
z-index: 10;
pointer-events: none;
cursor: move;
}
&.selected:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 4rpx solid $u-primary;
z-index: 10;
pointer-events: none;
cursor: move;
}
}
}

212
uni-app/src/addon/components/diy/group/index.vue

@ -0,0 +1,212 @@
<template>
<view class="diy-group" id="componentList">
<top-tabbar :scrollBool="diyGroup.componentsScrollBool.TopTabbar" v-if="data.global && Object.keys(data.global).length && data.global.topStatusBar && data.global.topStatusBar.isShow" ref="topTabbarRef" :data="data.global" />
<view v-for="(component, index) in data.value" :key="component.id"
@click="diyStore.changeCurrentIndex(index, component)"
:class="diyGroup.getComponentClass(index,component)" :style="component.pageStyle">
<view class="relative" :style="{ marginTop : component.margin.top < 0 ? (component.margin.top * 2) + 'rpx' : '0' }">
<!-- 装修模式下设置负上边距后超出的内容禁止选中设置 -->
<view v-if="diyGroup.isShowPlaceHolder(index,component)" class="absolute w-full z-1" :style="{ height : (component.margin.top * 2 * -1) + 'rpx' }" @click.stop="diyGroup.placeholderEvent"></view>
<template v-if="component.componentName == 'ActiveCube'">
<diy-active-cube ref="diyActiveCubeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ActiveCube" />
</template>
<template v-if="component.componentName == 'CarouselSearch'">
<diy-carousel-search ref="diyCarouselSearchRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.CarouselSearch" />
</template>
<template v-if="component.componentName == 'FloatBtn'">
<diy-float-btn ref="diyFloatBtnRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FloatBtn" />
</template>
<template v-if="component.componentName == 'FormAddress'">
<diy-form-address ref="diyFormAddressRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormAddress" />
</template>
<template v-if="component.componentName == 'FormCheckbox'">
<diy-form-checkbox ref="diyFormCheckboxRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormCheckbox" />
</template>
<template v-if="component.componentName == 'FormDate'">
<diy-form-date ref="diyFormDateRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDate" />
</template>
<template v-if="component.componentName == 'FormDateScope'">
<diy-form-date-scope ref="diyFormDateScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormDateScope" />
</template>
<template v-if="component.componentName == 'FormEmail'">
<diy-form-email ref="diyFormEmailRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormEmail" />
</template>
<template v-if="component.componentName == 'FormFile'">
<diy-form-file ref="diyFormFileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormFile" />
</template>
<template v-if="component.componentName == 'FormIdentity'">
<diy-form-identity ref="diyFormIdentityRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentity" />
</template>
<template v-if="component.componentName == 'FormIdentityPrivacy'">
<diy-form-identity-privacy ref="diyFormIdentityPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormIdentityPrivacy" />
</template>
<template v-if="component.componentName == 'FormImage'">
<diy-form-image ref="diyFormImageRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormImage" />
</template>
<template v-if="component.componentName == 'FormInput'">
<diy-form-input ref="diyFormInputRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormInput" />
</template>
<template v-if="component.componentName == 'FormLocation'">
<diy-form-location ref="diyFormLocationRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormLocation" />
</template>
<template v-if="component.componentName == 'FormMobile'">
<diy-form-mobile ref="diyFormMobileRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormMobile" />
</template>
<template v-if="component.componentName == 'FormNumber'">
<diy-form-number ref="diyFormNumberRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormNumber" />
</template>
<template v-if="component.componentName == 'FormPrivacy'">
<diy-form-privacy ref="diyFormPrivacyRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacy" />
</template>
<template v-if="component.componentName == 'FormPrivacyPop'">
<diy-form-privacy-pop ref="diyFormPrivacyPopRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormPrivacyPop" />
</template>
<template v-if="component.componentName == 'FormRadio'">
<diy-form-radio ref="diyFormRadioRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormRadio" />
</template>
<template v-if="component.componentName == 'FormSubmit'">
<diy-form-submit ref="diyFormSubmitRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormSubmit" />
</template>
<template v-if="component.componentName == 'FormTable'">
<diy-form-table ref="diyFormTableRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTable" />
</template>
<template v-if="component.componentName == 'FormTextarea'">
<diy-form-textarea ref="diyFormTextareaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTextarea" />
</template>
<template v-if="component.componentName == 'FormTime'">
<diy-form-time ref="diyFormTimeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTime" />
</template>
<template v-if="component.componentName == 'FormTimeScope'">
<diy-form-time-scope ref="diyFormTimeScopeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormTimeScope" />
</template>
<template v-if="component.componentName == 'FormVideo'">
<diy-form-video ref="diyFormVideoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormVideo" />
</template>
<template v-if="component.componentName == 'FormWechatName'">
<diy-form-wechat-name ref="diyFormWechatNameRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.FormWechatName" />
</template>
<template v-if="component.componentName == 'GraphicNav'">
<diy-graphic-nav ref="diyGraphicNavRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.GraphicNav" />
</template>
<template v-if="component.componentName == 'HorzBlank'">
<diy-horz-blank ref="diyHorzBlankRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzBlank" />
</template>
<template v-if="component.componentName == 'HorzLine'">
<diy-horz-line ref="diyHorzLineRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HorzLine" />
</template>
<template v-if="component.componentName == 'HotArea'">
<diy-hot-area ref="diyHotAreaRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.HotArea" />
</template>
<template v-if="component.componentName == 'ImageAds'">
<diy-image-ads ref="diyImageAdsRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ImageAds" />
</template>
<template v-if="component.componentName == 'MemberInfo'">
<diy-member-info ref="diyMemberInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberInfo" />
</template>
<template v-if="component.componentName == 'MemberLevel'">
<diy-member-level ref="diyMemberLevelRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.MemberLevel" />
</template>
<template v-if="component.componentName == 'Notice'">
<diy-notice ref="diyNoticeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Notice" />
</template>
<template v-if="component.componentName == 'PictureShow'">
<diy-picture-show ref="diyPictureShowRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.PictureShow" />
</template>
<template v-if="component.componentName == 'RichText'">
<diy-rich-text ref="diyRichTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RichText" />
</template>
<template v-if="component.componentName == 'RubikCube'">
<diy-rubik-cube ref="diyRubikCubeRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.RubikCube" />
</template>
<template v-if="component.componentName == 'Text'">
<diy-text ref="diyTextRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.Text" />
</template>
<template v-if="component.componentName == 'GoodsCoupon'">
<diy-goods-coupon ref="diyGoodsCouponRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.GoodsCoupon" />
</template>
<template v-if="component.componentName == 'GoodsList'">
<diy-goods-list ref="diyGoodsListRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.GoodsList" />
</template>
<template v-if="component.componentName == 'ManyGoodsList'">
<diy-many-goods-list ref="diyManyGoodsListRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ManyGoodsList" />
</template>
<template v-if="component.componentName == 'ShopExchangeGoods'">
<diy-shop-exchange-goods ref="diyShopExchangeGoodsRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopExchangeGoods" />
</template>
<template v-if="component.componentName == 'ShopExchangeInfo'">
<diy-shop-exchange-info ref="diyShopExchangeInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopExchangeInfo" />
</template>
<template v-if="component.componentName == 'ShopGoodsRanking'">
<diy-shop-goods-ranking ref="diyShopGoodsRankingRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopGoodsRanking" />
</template>
<template v-if="component.componentName == 'ShopGoodsRecommend'">
<diy-shop-goods-recommend ref="diyShopGoodsRecommendRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopGoodsRecommend" />
</template>
<template v-if="component.componentName == 'ShopMemberInfo'">
<diy-shop-member-info ref="diyShopMemberInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopMemberInfo" />
</template>
<template v-if="component.componentName == 'ShopNewcomer'">
<diy-shop-newcomer ref="diyShopNewcomerRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopNewcomer" />
</template>
<template v-if="component.componentName == 'ShopOrderInfo'">
<diy-shop-order-info ref="diyShopOrderInfoRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopOrderInfo" />
</template>
<template v-if="component.componentName == 'ShopSearch'">
<diy-shop-search ref="diyShopSearchRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.ShopSearch" />
</template>
<template v-if="component.componentName == 'SingleRecommend'">
<diy-single-recommend ref="diySingleRecommendRef" :component="component" :global="data.global" :index="index" :scrollBool="diyGroup.componentsScrollBool.SingleRecommend" />
</template>
</view>
</view>
<template v-if="diyStore.mode == '' && data.global && data.global.bottomTabBarSwitch">
<view class="pt-[20rpx]"></view>
<tabbar />
</template>
</view>
</template>
<script lang="ts" setup>
import diyGoodsCoupon from '@/addon/shop/components/diy/goods-coupon/index.vue';
import diyGoodsList from '@/addon/shop/components/diy/goods-list/index.vue';
import diyManyGoodsList from '@/addon/shop/components/diy/many-goods-list/index.vue';
import diyShopExchangeGoods from '@/addon/shop/components/diy/shop-exchange-goods/index.vue';
import diyShopExchangeInfo from '@/addon/shop/components/diy/shop-exchange-info/index.vue';
import diyShopGoodsRanking from '@/addon/shop/components/diy/shop-goods-ranking/index.vue';
import diyShopGoodsRecommend from '@/addon/shop/components/diy/shop-goods-recommend/index.vue';
import diyShopMemberInfo from '@/addon/shop/components/diy/shop-member-info/index.vue';
import diyShopNewcomer from '@/addon/shop/components/diy/shop-newcomer/index.vue';
import diyShopOrderInfo from '@/addon/shop/components/diy/shop-order-info/index.vue';
import diyShopSearch from '@/addon/shop/components/diy/shop-search/index.vue';
import diySingleRecommend from '@/addon/shop/components/diy/single-recommend/index.vue';
import topTabbar from '@/components/top-tabbar/top-tabbar.vue'
import useDiyStore from '@/app/stores/diy';
import { useDiyGroup } from './useDiyGroup';
import { ref,getCurrentInstance } from 'vue';
const props = defineProps(['data']);
const instance: any = getCurrentInstance();
const getFormRef = () => {
return {
componentRefs: instance.refs
}
}
const diyStore = useDiyStore();
const diyGroup = useDiyGroup({
...props,
getFormRef
});
const data = ref(diyGroup.data);
//
diyGroup.onMounted();
//
diyGroup.onPageScroll();
defineExpose({
refresh: diyGroup.refresh,
getFormRef
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

185
uni-app/src/addon/components/diy/group/useDiyGroup.ts

@ -0,0 +1,185 @@
import { ref, onMounted, nextTick, computed } from 'vue';
import Sortable from 'sortablejs';
import { range } from 'lodash-es';
import { onPageScroll, onHide, onShow } from '@dcloudio/uni-app';
import useDiyStore from '@/app/stores/diy';
export function useDiyGroup(params: any = {}) {
let scrollVal: any = ""; //组件滚动值集合
const componentsScrollBool: any = ref({}); //组件是否根据滚动进行相应改变
const diyStore = useDiyStore();
const positionFixed = ref(['fixed', 'top_fixed', 'right_fixed', 'bottom_fixed', 'left_fixed']);
const data = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore;
} else {
return params.data;
}
})
const getComponentClass = (index: any, component: any) => {
let obj: any = {
relative: true,
selected: diyStore.currentIndex == index,
decorate: diyStore.mode == 'decorate'
}
obj['top-fixed-' + diyStore.topFixedStatus] = true;
if (component.position && positionFixed.value.indexOf(component.position) != -1) {
// 找出置顶组件,设置禁止拖动
obj['ignore-draggable-element'] = true;
} else {
obj['draggable-element'] = true;
}
if (component.componentName == 'ImageAds') {
obj['overflow-hidden'] = true
}
return obj;
}
// 是否显示占位区域,用于禁止选中负上边距的内容
const isShowPlaceHolder = (index: any, component: any) => {
// #ifdef H5
if (diyStore.mode == 'decorate') {
let el: any = document.getElementById('componentList');
if (el && el.children.length && el.children[index]) {
let height = el.children[index].offsetHeight;
let top = 0;
if (component.margin.top < 0) {
top = component.margin.top * 2 * -1;
// 若负上边距大于组件的高度,则允许选中进行装修
if (top > height) {
return false;
}
}
}
return true;
}
// #endif
return false;
}
// 监听页面加载完成
const onMountedLifeCycle = () => {
onMounted(() => {
// #ifdef H5
if (diyStore.mode == 'decorate') {
var el: any = document.getElementById('componentList');
const sortable = Sortable.create(el, {
draggable: '.draggable-element',
animation: 200,
// 结束拖拽
onEnd: event => {
let temp = diyStore.value[event.oldIndex!];
diyStore.value.splice(event.oldIndex!, 1);
diyStore.value.splice(event.newIndex!, 0, temp);
nextTick(() => {
sortable.sort(range(diyStore.value.length).map((value: any) => {
return value.toString();
}));
diyStore.postMessage(event.newIndex, diyStore.value[event.newIndex]);
});
}
});
}
// #endif
nextTick(() => {
setTimeout(() => {
// 初始化组件滚动值
scrollVal = uni.getStorageSync('componentsScrollValGroup');
if (scrollVal) {
for (let key in scrollVal) {
componentsScrollBool.value[key] = -1;
}
}
}, 500)
});
});
}
// 页面onShow调用时,也会触发改方法
const refresh = () => {
nextTick(() => {
let time: any = null;
let fn = () => {
diyStore.componentRefs = params.getFormRef().componentRefs;
data.value.componentRefs = params.getFormRef().componentRefs;
params.getFormRef().componentRefs.topTabbarRef?.refresh();
if (time) clearInterval(time);
}
let getPass = () => {
let pass = false;
try {
params.getFormRef()
pass = true;
} catch (e) {
pass = false;
}
if (pass) {
fn();
}
return pass;
}
if (!getPass()) {
time = setInterval(() => {
getPass()
}, 100)
}
})
}
// 空函数,禁止选中
const placeholderEvent = () => {
}
const isPagesHide = ref(false)
onShow(() => {
isPagesHide.value = false;
})
onHide(() => {
isPagesHide.value = true;
})
// 监听滚动事件
const scrollValStr = ref()
const onPageScrollLifeCycle = () => {
onPageScroll((e) => {
if (scrollVal && !isPagesHide.value) {
for (let key in scrollVal) {
if (e.scrollTop <= 0) {
// -1 表示页面滚动值小于零,组件随页面下拉而下来
componentsScrollBool.value[key] = -1;
} else if (e.scrollTop > scrollVal[key]) {
// 1 表示页面滚动值大于传入滚动值,组件随页面上拉背景、文字颜色等采用滚动后的变量
componentsScrollBool.value[key] = 1;
} else {
// 2 表示页面滚动值小于传入滚动值,组件随页面下拉背景、文字颜色等采用滚动前的变量
componentsScrollBool.value[key] = 2
}
}
}
})
}
return {
scrollV: scrollValStr.value,
data: data.value,
componentsScrollBool: componentsScrollBool.value,
placeholderEvent,
refresh,
isShowPlaceHolder,
getComponentClass,
onPageScroll: onPageScrollLifeCycle,
onMounted: onMountedLifeCycle
}
}

59
uni-app/src/addon/shop/api/cart.ts

@ -0,0 +1,59 @@
import request from '@/utils/request'
/**
*
*/
export function addCart(data: AnyObject) {
return request.post(`shop/cart`, data)
}
/**
*
*/
export function editCart(data: AnyObject) {
return request.put(`shop/cart`, data)
}
/**
*
*/
export function deleteCart(data: AnyObject) {
return request.put(`shop/cart/delete`, data)
}
/**
*
*/
export function clearCart() {
return request.delete(`shop/cart/clear`)
}
/**
*
*/
export function getCartList(params: Record<string, any>) {
return request.get(`shop/cart`, params)
}
/**
*
*/
export function getCartGoodsList(params: Record<string, any>) {
return request.get(`shop/cart/goods`, params)
}
/**
*
*/
export function getCartSum(params: Record<string, any>) {
return request.get(`shop/cart/sum`, params)
}
/**
*
*/
export function getCartCalculate(params: Record<string, any>) {
return request.get(`shop/cart/calculate`, params)
}

8
uni-app/src/addon/shop/api/config.ts

@ -0,0 +1,8 @@
import request from '@/utils/request'
/**
*
*/
export function getInvoiceConfig() {
return request.get(`shop/config/invoice`)
}

65
uni-app/src/addon/shop/api/coupon.ts

@ -0,0 +1,65 @@
import request from '@/utils/request'
/**
*
*/
export function getShopCouponList(params: Record<string, any>) {
return request.get(`shop/coupon`, params)
}
/**
*
*/
export function getShopCouponInfo(id: number) {
return request.get(`shop/coupon/${ id }`)
}
/**
*
*/
export function getShopCouponQrocde(id: number) {
return request.get(`shop/coupon/qrcode/${ id }`)
}
/**
*
*/
export function getCoupon(params: Record<string, any>) {
return request.post(`shop/coupon`, params, { showSuccessMessage: true })
}
/**
*
*/
export function getMyCouponList(params: Record<string, any>) {
return request.get(`shop/member/coupon`, params)
}
/**
*
*/
export function getShopCouponComponents(params: Record<string, any>) {
return request.get(`shop/coupon/components`, params)
}
/**
*
* status 1使2使34
*/
export function getMyCouponCount(params: Record<string, any>) {
return request.get(`shop/member/coupon/count`, params)
}
/**
*
*/
export function getMyCouponType() {
return request.get(`shop/coupon_type`)
}
/**
*
*/
export function getMyCouponStatusCount() {
return request.get(`shop/member/coupon/status_count`)
}

25
uni-app/src/addon/shop/api/discount.ts

@ -0,0 +1,25 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getActiveDiscountConfig() {
return request.get(`shop/discount/config`);
}
/**
*
* @returns
*/
export function getActiveDiscountList(params: Record<string, any>) {
return request.get(`shop/discount`, params)
}
/**
*
* @returns
*/
export function getActiveDiscountGoodsList(params: Record<string, any>) {
return request.get(`shop/discount/goods`, params)
}

29
uni-app/src/addon/shop/api/evaluate.ts

@ -0,0 +1,29 @@
import request from '@/utils/request'
/**
*
*/
export function getEvaluateList(params: Record<string, any>) {
return request.get(`shop/goods/evaluate`, params)
}
/**
*
*/
export function getEvaluateInfo(id: any) {
return request.get(`shop/goods/evaluate/${ id }`)
}
/**
*
*/
export function setOrderEvaluate(params: Record<string, any>) {
return request.post('shop/goods/evaluate', params, { showSuccessMessage: true })
}
/**
*
*/
export function getOrderEvaluate(id: any) {
return request.get(`shop/order/evaluate/${ id }`)
}

107
uni-app/src/addon/shop/api/goods.ts

@ -0,0 +1,107 @@
import request from '@/utils/request'
/**
*
*/
export function getGoodsCategoryConfig() {
return request.get(`shop/goods/category/config`)
}
/**
*
*/
export function getGoodsCategoryTree() {
return request.get(`shop/goods/category/tree`)
}
/**
*
*/
export function getGoodsCategoryList(params: Record<string, any>) {
return request.get(`shop/goods/category/list`, params)
}
/**
*
*/
export function getGoodsPages(params: Record<string, any>) {
return request.get(`shop/goods/pages`, params)
}
/**
*
*/
export function getGoodsDetail(params: Record<string, any>) {
return request.get(`shop/goods/detail`, params)
}
/**
*
*/
export function getGoodsSku(sku_id: any) {
return request.get(`shop/goods/sku/${ sku_id }`)
}
/**
*
*/
export function getCollectList(params: Record<string, any>) {
return request.get(`shop/goods/collect`, params)
}
/**
*
*/
export function cancelCollect(params: Record<string, any>) {
return request.put(`shop/goods/collect`, params, { showSuccessMessage: true })
}
/**
*
*/
export function collect(goods_id: any) {
return request.post(`shop/goods/collect/${ goods_id }`)
}
/**
*
*/
export function getEvaluateList(goods_id: any) {
return request.get(`shop/goods/evaluate/list`, { goods_id })
}
/**
*
*/
export function getGoodsComponents(params: Record<string, any>) {
return request.get(`shop/goods/components`, params)
}
/**
*
*/
export function getManjian(params: Record<string, any>) {
return request.get(`shop/manjian/info`, params)
}
/**
*
*/
export function browse(params: Record<string, any>) {
return request.post(`shop/goods/browse`, params, { showSuccessMessage: false })
}
/**
*
*/
export function getBrowse(params: Record<string, any>) {
return request.get(`shop/goods/browse`, params)
}
/**
*
*/
export function delBrowse(params: Record<string, any>) {
return request.delete(`shop/goods/browse`, params)
}

16
uni-app/src/addon/shop/api/newcomer.ts

@ -0,0 +1,16 @@
import request from '@/utils/request'
// 分享专区列表
export function getNewcomerGoodsList(params: Record<string, any>) {
return request.get(`shop/newcomer/goods`, params)
}
export function getNewcomersConfig() {
return request.get(`shop/newcomer/config`)
}
// 首页新人专享列表
export function getNewcomersComponentsList(params: Record<string, any>) {
return request.get(`shop/newcomer/goods/components`, params)
}

86
uni-app/src/addon/shop/api/order.ts

@ -0,0 +1,86 @@
import request from '@/utils/request'
/***************************************************** 订单列表 ****************************************************/
/**
*
*/
export function getShopOrderConfig() {
return request.get(`shop/order/config`)
}
/**
*
*/
export function getShopOrderStatus() {
return request.get(`shop/order/status`)
}
/**
*
*/
export function getShopOrder(params: Record<string, any>) {
return request.get(`shop/order`, params)
}
/**
*
*/
export function getShopOrderNum() {
return request.get(`shop/order/num`)
}
/**
*
*/
export function getShopOrderDetail(order_id: any) {
return request.get(`shop/order/${ order_id }`)
}
/**
*
*/
export function orderClose(order_id: number) {
return request.put(`shop/order/close/${ order_id }`)
}
/**
*
*/
export function orderFinish(order_id: number) {
return request.put(`shop/order/finish/${ order_id }`)
}
/**
*
*/
export function orderCreateCalculate(params: Record<string, any>) {
return request.get('shop/order_create/calculate', params)
}
/**
*
*/
export function orderCreate(params: Record<string, any>) {
return request.post('shop/order_create/create', params)
}
/**
*
*/
export function orderCoupon(params: Record<string, any>) {
return request.get('shop/order_create/coupon', params)
}
/**
*
*/
export function getStoreList(params: Record<string, any>) {
return request.get('shop/order_create/store', params)
}
/**
*
*/
export function getMaterialflowList(params: Record<string, any>) {
return request.get('shop/order/logistics', params)
}

47
uni-app/src/addon/shop/api/point.ts

@ -0,0 +1,47 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getExchangePoint() {
return request.get(`shop/exchange/point`);
}
/**
*
* @returns
*/
export function getExchangeComponentsList(params: Record<string, any>) {
return request.get(`shop/exchange/components`, params)
}
/**
*
* @returns
*/
export function getExchangeGoodsList(params: Record<string, any>) {
return request.get(`shop/exchange`, params)
}
/**
*
*/
export function getExchangeGoodsDetail(id: any) {
return request.get(`shop/exchange/goods/${ id }`)
}
/**
*
*/
export function orderCreateCalculate(params: Record<string, any>) {
return request.get('shop/exchange_order/calculate', params)
}
/**
*
*/
export function orderCreate(params: Record<string, any>) {
return request.post('shop/exchange_order/create', params)
}

29
uni-app/src/addon/shop/api/rank.ts

@ -0,0 +1,29 @@
import request from '@/utils/request'
// 榜单分类列表
export function getRankList() {
return request.get(`shop/rank`)
}
// 榜单商品列表
export function getRankGoodsList(params: Record<string, any>) {
return request.get(`shop/rank/goods`, params)
}
// 获取排行榜配置
export function getRankConfig() {
return request.get(`shop/rank/getRankConfig`)
}
// 榜单组件商品列表
export function getRankComponentsGoodsList(params: Record<string, any>) {
return request.get(`shop/rank/components`, params)
}

71
uni-app/src/addon/shop/api/refund.ts

@ -0,0 +1,71 @@
import request from '@/utils/request'
/**
* 退
*/
export function applyRefund(params: Record<string, any>) {
return request.post(`shop/refund/apply`, params, { showSuccessMessage: true })
}
/**
* 退
*/
export function editRefund(params: Record<string, any>) {
return request.put(`shop/refund/${ params.order_refund_no }`, params, { showSuccessMessage: true })
}
/**
* 退退
*/
export function refundDelivery(params: Record<string, any>) {
return request.post(`shop/refund/delivery/${ params.order_refund_no }`, params, { showSuccessMessage: true })
}
/**
* 退
*/
export function editRefundDelivery(params: Record<string, any>) {
return request.put(`shop/refund/delivery/${ params.order_refund_no }`, params, { showSuccessMessage: true })
}
/**
* 退
*/
export function getRefundReason() {
return request.get('shop/refund/reason')
}
/**
* 退
*/
export function getRefundList() {
return request.get('shop/order/refund')
}
/**
* 退
*/
export function getRefundDetail(orderRefundNo: string) {
return request.get(`shop/order/refund/${ orderRefundNo }`)
}
/**
* 退
*/
export function getRefundMoney(params: Record<string, any>) {
return request.get(`shop/refund/refund_data`, params)
}
/**
* 退
*/
export function getRefundMoneyAgain(params: Record<string, any>) {
return request.get(`shop/refund/refund_data_by_no`, params)
}
/**
*
*/
export function closeRefund(orderRefundNo: string) {
return request.put(`shop/refund/close/${ orderRefundNo }`, {}, { showSuccessMessage: true })
}

8
uni-app/src/addon/shop/api/shop.ts

@ -0,0 +1,8 @@
import request from '@/utils/request'
/**
*
*/
export function getEvaluateConfig() {
return request.get(`shop/goods/evaluate/config`)
}

382
uni-app/src/addon/shop/components/diy/goods-coupon/index.vue

@ -0,0 +1,382 @@
<template>
<x-skeleton :type="skeleton.type" :loading="skeleton.loading" :config="skeleton.config" v-if="couponList && Object.keys(couponList).length > 0">
<view :style="warpCss" class="overflow-hidden">
<view v-if="diyComponent.style == 'style-1'" class="coupon-wrap style-1 relative">
<scroll-view scroll-x="true" class="coupon-list" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/style1_bg2.png') + ')','background-size':'100%','background-repeat':'no-repeat'}">
<view class="coupon-class">
<block v-if="couponList.length > 1">
<view v-for="(item,index) in couponList" :key="index" class="rounded-[16rpx] box-border pt-[14rpx] inline-flex flex-col items-center relative w-[150rpx] h-[130rpx]" :class="{'mr-[20rpx]': index != couponList.length-1}" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/coupon_item_bg.png') + ')','background-size':'100%','background-repeat':'no-repeat'}" @click="couponItemLink(item)">
<view class="truncate w-full flex items-baseline justify-center price-font text-[var(--price-text-color)]">
<text class="text-[26rpx] font-500"></text>
<text class="text-[36rpx] truncate font-500">{{ parseFloat(item.price) }}</text>
</view>
<view class="text-[#303133] text-[20rpx] mt-[12rpx]">{{ item.min_condition_money == '0.00' ? '无门槛' : ('满'+parseFloat(item.min_condition_money)+'元可用') }}</view>
<view class="mt-[auto] rounded-b-[12rpx] text-[#f2333c] text-[20rpx] w-[100%] h-[36rpx] flex items-center justify-center bg-[#fff5f2]">{{item.type_name}}</view>
</view>
</block>
<block v-else>
<view v-for="(item,index) in couponList" :key="index" class="rounded-[16rpx] box-border pt-[14rpx] pl-[44rpx] pr-[44rpx] inline-flex items-center justify-between relative w-[100%] h-[130rpx]" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/style1_bg4.png') + ')','background-size':'100%','background-repeat':'no-repeat'}" @click="couponItemLink(item)">
<view class="flex price-font text-[var(--price-text-color)] items-baseline">
<text class="text-[36rpx] mt-[16rpx] mr-[4rpx]"></text>
<text class="text-[85rpx] font-500 max-w-[170rpx] truncate">{{parseFloat(item.price)}}</text>
</view>
<view class="border-0 border-dashed border-r-[2rpx] border-[#FF323C] absolute left-[40%] top-[46rpx] bottom-0 w-[2rpx]"></view>
<view class="w-[270rpx]">
<view class="flex items-center mt-[auto]">
<text class="rounded-[4rpx] bg-[#fff3f0] text-[#f2333c] border-[2rpx] border-solid border-[#f2333c] text-[22rpx] px-[6rpx] pb-[4rpx] pt-[6rpx] flex items-center justify-center whitespace-nowrap">{{item.type_name}}</text>
<text class="ml-[4rpx] text-[#f2333c] max-w-[184rpx] truncate">{{item.title}}</text>
</view>
<view class="text-[#f2333c] text-[30rpx] font-500 mt-[10rpx] w-[270rpx] truncate">{{item.min_condition_money == '0.00' ? '无门槛' : ('消费满'+parseFloat(item.min_condition_money)+'元可用') }}</view>
</view>
</view>
</block>
</view>
</scroll-view>
<view class="w-[100%] h-[130rpx] pt-[24rpx] px-[26rpx] box-border flex items-center justify-between absolute left-0 right-0 bottom-0" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/style1_bg.png') + ')','background-size':'100% 130rpx','background-repeat':'no-repeat'}">
<view class="flex flex-col">
<text class="text-[30rpx] text-[#fff] font-400">{{ diyComponent.couponTitle }}</text>
<text class="text-[20rpx] text-[rgba(255,255,255,.8)] mt-[10rpx]">{{ diyComponent.couponSubTitle }}</text>
</view>
<text v-if="diyComponent.btnText" @click="toLink('/addon/shop/pages/coupon/list')" class="bg-[#fff] flex items-center justify-center text-[#FF4142] text-[22rpx] min-w-[100rpx] px-[24rpx] box-border h-[50rpx] coupon-buy-btn">{{diyComponent.btnText}}</text>
</view>
</view>
<view v-else-if="diyComponent.style == 'style-2'" class="coupon-wrap style-2 relative">
<scroll-view scroll-x="true" class="coupon-list">
<view v-for="(item,index) in couponList" :key="index" class="box-border pt-[14rpx] inline-flex flex-col items-center relative w-[140rpx] h-[130rpx] rounded-[10rpx]" :class="{'mr-[20rpx]': index != couponList.length-1, 'mr-[290rpx]': index == couponList.length-1}" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/coupon_item_bg.png') + ')','background-size':'100%','background-repeat':'no-repeat'}" @click="couponItemLink(item)">
<view class="flex items-baseline justify-center w-full truncate price-font text-[var(--price-text-color)]">
<text class="text-[24rpx]"></text>
<text class="text-[38rpx] font-bold truncate">{{parseFloat(item.price)}}</text>
</view>
<view class="text-[#303133] text-[20rpx] truncate max-w-[120rpx] mt-[12rpx]">{{item.min_condition_money == '0.00' ? '无门槛' : ('满'+parseFloat(item.min_condition_money)+'元可用') }}</view>
<view class="mt-[auto] rounded-b-[12rpx] text-[#f2333c] text-[20rpx] w-[100%] h-[36rpx] flex items-center justify-center bg-[#fff5f2]">{{item.type_name}}</view>
</view>
</scroll-view>
<view class="w-[290rpx] h-[170rpx] py-[20rpx] pl-[30rpx] box-border flex flex-col items-center justify-between absolute right-0 bottom-0" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/style2_bg.png') + ')','background-size':'290rpx 170rpx','background-repeat':'no-repeat'}">
<text class="text-[30rpx] text-[#fff] font-500">{{ diyComponent.couponTitle }}</text>
<text class="text-[20rpx] text-[rgba(255,255,255,.8)] mt-[14rpx]">{{ diyComponent.couponSubTitle }}</text>
<text v-if="diyComponent.btnText" @click="toLink('/addon/shop/pages/coupon/list')" class="bg-[#fff] text-[#FF4142] text-[22rpx] min-w-[100rpx] px-[24rpx] box-border h-[50rpx] leading-[50rpx] text-center coupon-buy-btn mt-auto">{{diyComponent.btnText}}</text>
</view>
</view>
<view v-else-if="diyComponent.style == 'style-3'" class="coupon-wrap style-3 relative" :style="{'background-image':'url(' + img('addon/shop/diy/goods_coupon/style3_bg.jpg') + ')','background-size':'100% 204rpx','background-repeat':'no-repeat'}">
<view class="desc flex flex-col">
<text class="text-[30rpx] text-[#fff] font-500">{{ diyComponent.couponTitle }}</text>
<text class="text-[22rpx] text-[rgba(255,255,255,.8)] mt-[10rpx]">{{ diyComponent.couponSubTitle }}</text>
<text v-if="diyComponent.btnText" @click="toLink('/addon/shop/pages/coupon/list')" class="bg-[#fff] text-[#FF4142] text-[24rpx] w-[140rpx] box-border h-[50rpx] leading-[50rpx] text-center coupon-buy-btn mt-auto">{{diyComponent.btnText}}</text>
</view>
<scroll-view scroll-x="true" class="coupon-list" v-if="couponList.length > 1">
<view v-for="(item,index) in couponList" :key="index" class="bg-[#fff] box-border p-[8rpx] pb-[12rpx] inline-flex flex-col items-center relative rounded-[20rpx] ml-[12rpx]" @click="couponItemLink(item)">
<view class="coupon-item-content">
<view class="text-[20rpx] text-[#fff]">{{item.type_name}}</view>
<view class="mt-[auto] flex items-baseline justify-center w-full truncate price-font text-[#fff]">
<text class="text-[24rpx]"></text>
<text class="text-[44rpx] font-bold truncate">{{parseFloat(item.price)}}</text>
</view>
</view>
<view class="text-[#303133] text-[22rpx] truncate max-w-[120rpx] mt-[12rpx]">{{item.min_condition_money == '0.00' ? '无门槛' : ('满'+parseFloat(item.min_condition_money)+'元可用') }}</view>
</view>
</scroll-view>
<view v-if="couponList.length == 1" class="bg-[#fff] box-border p-[10rpx] relative rounded-[20rpx] single-coupon flex-1" @click="couponItemLink(couponList[0])">
<view class="flex items-center coupon-item-content">
<view class="coupon-left flex items-center justify-center text-[#fff] w-[156rpx] mr-[30rpx]">
<text class="text-[24rpx]"></text>
<text class="text-[44rpx] font-[500]">{{parseFloat(couponList[0].price)}}</text>
</view>
<view class="flex flex-col">
<view class="text-[#fff] text-[28rpx] mb-[14rpx]">{{couponList[0].min_condition_money == '0.00' ? '无门槛' : ('满'+parseFloat(couponList[0].min_condition_money)+'元可用') }}</view>
<view class="flex items-center">
<text class="bg-[#fff] mr-[10rpx] text-[red] text-[20rpx] px-[10rpx] py-[8rpx] rounded-[20rpx]">{{couponList[0].type_name}}</text>
<text class="text-[#fff] text-[24rpx]">店铺优惠券</text>
</view>
</view>
</view>
</view>
</view>
<view v-else-if="diyComponent.style == 'style-4'" class="coupon-wrap style-4 relative" :style="couponStyle4Css">
<view class="desc flex items-center pt-[6rpx] pb-[26rpx]" @click="toLink('/addon/shop/pages/coupon/list')">
<text class="text-[32rpx] text-[#fff] font-500" :style="{color: diyComponent.titleColor}">{{ diyComponent.couponTitle }}</text>
<text class="text-[22rpx] text-[rgba(255,255,255,.8)] ml-[10rpx]" :style="{color: diyComponent.subTitleColor}">{{ diyComponent.couponSubTitle }}</text>
</view>
<scroll-view scroll-x="true" class="coupon-list">
<view v-for="(item,index) in couponList" :key="index" class="px-[10rpx] h-[120rpx] inline-flex items-center relative mr-[12rpx] coupon-item box-border min-w-[310rpx]" :style="{'background-color': diyComponent.couponItem.bgColor, 'border-radius': ((diyComponent.couponItem.aroundRadius*2)+'rpx')}" @click="couponItemLink(item)">
<view class="flex min-w-[110rpx] max-w-[120rpx] items-baseline justify-center truncate price-font mr-[10rpx]" :style="{'color': diyComponent.couponItem.moneyColor}">
<text class="text-[26rpx]"></text>
<text class="text-[46rpx] font-bold truncate">{{parseFloat(item.price)}}</text>
</view>
<view class="flex flex-col">
<view class="text-[28rpx] font-500" :style="{'color': diyComponent.couponItem.textColor}">{{item.type_name}}</view>
<view class="text-[#666] text-[24rpx] truncate max-w-[180rpx] mt-[12rpx]" :style="{'color': diyComponent.couponItem.subTextColor}">{{item.min_condition_money == '0.00' ? '无门槛' : ('满'+parseFloat(item.min_condition_money)+'元可用') }}</view>
</view>
</view>
</scroll-view>
</view>
</view>
</x-skeleton>
</template>
<script setup lang="ts">
//
import { ref, reactive, computed, watch, onMounted } from 'vue';
import { img, redirect } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { useLogin } from '@/hooks/useLogin';
import useMemberStore from '@/stores/member'
import { getShopCouponComponents,getCoupon } from '@/addon/shop/api/coupon';
const props = defineProps(['component', 'index']);
const diyStore = useDiyStore();
const memberStore = useMemberStore()
const userInfo = computed(() => memberStore.info)
const skeleton = reactive({
type: 'banner',
loading: false,
config: {
gridRows: 1,
gridRowsGap: '0rpx',
headHeight: '170rpx'
}
})
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const couponStyle4Css = computed(()=>{
var style = '';
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + (diyComponent.value.componentStartBgColor || diyComponent.value.componentEndBgColor) + ';';
return style;
})
const toLink = (url: any)=>{
if (diyStore.mode == 'decorate') return;
redirect({ url })
}
const couponList: any = ref([])
const getShopCouponListFn = () => {
let data: object = {
num: diyComponent.value.source == 'all' ? diyComponent.value.num : '',
coupon_ids: diyComponent.value.source == 'custom' ? diyComponent.value.couponIds : '',
};
getShopCouponComponents(data).then((res: any) => {
couponList.value = res.data
skeleton.loading = false;
//
// if(couponList.value.length == 0) {
// diyComponent.value.pageStyle = '';
// }
})
}
const couponItemLink = (data:any)=> {
// redirect({ url: '/addon/shop/pages/coupon/detail', param: { coupon_id: data.id } })
collecting(data.id)
}
onMounted(() => {
refresh();
});
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
let obj = {
title: '满减券',
type_name: '通用券',
price: 100,
min_condition_money: 0,
};
for (let i = 0; i < 4; i++) {
couponList.value.push(obj);
}
} else {
getShopCouponListFn();
}
}
const collecting = (coupon_id: any) => {
if (diyStore.mode == 'decorate') return;
if (!userInfo.value) {
useLogin().setLoginBack({ url: '/addon/shop/pages/coupon/list' })
return false
}
getCoupon({ coupon_id, number: 1 }).then(res => {
// detail.value.btnType = 'using'
})
}
</script>
<style lang="scss" scoped>
.coupon-wrap{
&.style-1{
.coupon-list{
position: relative;
height: 270rpx;
width: 100%;
white-space: nowrap;
padding: 30rpx 40rpx 0;
box-sizing: border-box;
&::before{
content: "";
position: absolute;
top: 0;
left: 20rpx;
right: 20rpx;
bottom: 0;
background: linear-gradient(#FEF9EC, #FCD9A5);
border-radius: 24rpx;
}
}
.coupon-buy-btn{
border-radius: 50rpx;
}
}
&.style-2{
.coupon-list{
position: relative;
height: 170rpx;
width: 100%;
white-space: nowrap;
padding: 20rpx 0 20rpx 20rpx;
box-sizing: border-box;
overflow: hidden;
background: linear-gradient(#EE3928, #EF3F30);
}
.coupon-buy-btn{
border-radius: 50rpx;
}
}
&.style-3{
display: flex;
align-items: center;
padding: 20rpx;
.desc{
padding-top: 12rpx;
height: 164rpx;
margin-right: 10rpx;
box-sizing: border-box;
}
.coupon-list{
flex: 1;
position: relative;
white-space: nowrap;
box-sizing: border-box;
overflow: hidden;
.coupon-item-content{
position: relative;
background: linear-gradient( 160deg, #FD5F2F 0%, #F6370F 100%);
width: 146rpx;
height: 108rpx;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12rpx 0 14rpx;
box-sizing: border-box;
&::after{
content: "";
position: absolute;
border: 12rpx solid #fff;
border-right-color: transparent;
border-top-color: transparent;
border-radius: 50%;
top: 50%;
transform: rotate(45deg) translateY(-50%);
right: -5rpx;
}
&::before{
content: "";
position: absolute;
border: 12rpx solid #fff;
border-left-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
top: 50%;
transform: rotate(45deg) translateY(-50%);
left: -22rpx;
}
}
}
.single-coupon{
.coupon-item-content{
position: relative;
background: linear-gradient( 160deg, #FD5F2F 0%, #F6370F 100%);
height: 144rpx;
border-radius: 12rpx;
padding: 12rpx 0 14rpx;
box-sizing: border-box;
&::after{
content: "";
position: absolute;
border: 14rpx solid #fff;
border-right-color: transparent;
border-top-color: transparent;
border-radius: 50%;
left: 139rpx;
transform: rotate(-45deg);
top: -18rpx;
}
&::before{
content: "";
position: absolute;
border: 14rpx solid #fff;
border-left-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
left: 139rpx;
transform: rotate(-45deg);
bottom: -16rpx;
}
.coupon-left{
position: relative;
&::after{
content: '';
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
border-right: 2rpx dashed #fff;
width: 2rpx;
height: 88rpx;
}
}
}
}
.coupon-buy-btn{
border-radius: 50rpx;
}
}
&.style-4{
padding: 20rpx 24rpx;
.coupon-list{
flex: 1;
position: relative;
white-space: nowrap;
box-sizing: border-box;
overflow: hidden;
.coupon-item{
width: calc(50% - 6rpx);
}
}
}
}
</style>

434
uni-app/src/addon/shop/components/diy/goods-list/index.vue

@ -0,0 +1,434 @@
<template>
<x-skeleton :type="skeleton.type" :loading="skeleton.loading" :config="skeleton.config">
<view :style="warpCss" class="overflow-hidden">
<view :style="maskLayer"></view>
<view :class="{'diy-shop-goods-list relative flex flex-wrap justify-between': diyComponent.style != 'style-2', 'biserial-goods-list': diyComponent.style == 'style-2'}">
<block v-if="diyComponent.style == 'style-1'">
<view class="bg-white w-full flex p-[20rpx] overflow-hidden" :class="{ 'mt-[20rpx]': index > 0 }" :style="itemCss" v-for="(item,index) in goodsList" :key="item.goods_id" @click="toLink(item)">
<u--image radius="var(--goods-rounded-big)" width="200rpx" height="200rpx" :src="img(item.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="w-[200rpx] h-[200rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="flex-1 flex flex-col ml-[20rpx] py-[6rpx] relative">
<view class="text-[28rpx] leading-[40rpx] text-[#303133] multi-hidden mb-[10rpx]" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }" v-if="diyComponent.goodsNameStyle.control">
<view class="brand-tag" v-if="item.goods_brand">
{{item.goods_brand.brand_name}}
</view>
{{item.goods_name}}
</view>
<view v-if="item.goods_label_name && item.goods_label_name.length && diyComponent.labelStyle.control" class="flex flex-wrap mb-[10rpx]">
<template v-for="(tagItem, tagIndex) in item.goods_label_name">
<image class="img-tag" v-if="tagItem.style_type == 'icon' && tagItem.icon" :src="img(tagItem.icon)" mode="heightFix" @error="diyGoods.error(tagItem,'icon')"></image>
<view class="base-tag" v-else-if="tagItem.style_type == 'diy' || !tagItem.icon" :style="diyGoods.baseTagStyle(tagItem)">
{{tagItem.label_name}}
</view>
</template>
</view>
<view class="mt-auto flex justify-between items-center">
<view class="flex flex-col">
<view class="flex items-baseline leading-[1]" v-if="diyComponent.priceStyle.control">
<view class="font-bold text-[var(--price-text-color)] price-font block truncate max-w-[350rpx]" :style="{ color : diyComponent.priceStyle.color }">
<text class="text-[24rpx] font-500 mr-[4rpx]"></text>
<text class="text-[40rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2)}}</text>
</view>
<image v-if="diyGoods.priceType(item) == 'member_price'" class="max-w-[50rpx] h-[28rpx] ml-[6rpx]" :src="img('addon/shop/VIP.png')" mode="heightFix" />
</view>
<text v-if="diyComponent.saleStyle.control" class="mt-[8rpx] text-[22rpx] text-[var(--text-color-light9)]" :style="{ color : diyComponent.saleStyle.color }">已售{{item.sale_num}}{{item.unit || ''}}</text>
</view>
<view v-if="diyComponent.btnStyle.control" class="absolute right-[0] bottom-[0]">
<view v-if="diyComponent.btnStyle.style == 'button'" :style="goodsBtnCss" class="px-[18rpx] min-w-[100rpx] box-border h-[48rpx] flex items-center justify-center">
<text class="text-[20rpx]">{{diyComponent.btnStyle.text}}</text>
</view>
<view v-else :style="goodsBtnCss" class="w-[50rpx] h-[50rpx] rounded-[50%] flex items-center justify-center">
<text :class="[diyComponent.btnStyle.style]" class="nc-iconfont text-[30rpx]"></text>
</view>
</view>
</view>
</view>
</view>
</block>
<block v-if="diyComponent.style == 'style-2'">
<view>
<template v-for="(item,index) in goodsList">
<view v-if="(index%2) == 0" class="flex flex-col bg-[#fff] box-border overflow-hidden" :class="{'mt-[24rpx]': index > 1}" :style="itemCss" @click="toLink(item)">
<u--image :width="style2Width" :height="style2Width" :src="img(item.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image :style="{'width': style2Width,'height': style2Width}" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="relative min-h-[44rpx] px-[16rpx] flex-1 pt-[16rpx] pb-[20rpx] flex flex-col justify-between">
<view class="text-[#303133] leading-[40rpx] text-[28rpx] multi-hidden" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }" v-if="diyComponent.goodsNameStyle.control">
<view class="brand-tag" v-if="item.goods_brand">
{{item.goods_brand.brand_name}}
</view>
{{item.goods_name}}
</view>
<view v-if="item.goods_label_name && item.goods_label_name.length && diyComponent.labelStyle.control" class="flex flex-wrap">
<template v-for="(tagItem, tagIndex) in item.goods_label_name">
<image class="img-tag" v-if="tagItem.style_type == 'icon' && tagItem.icon" :src="img(tagItem.icon)" mode="heightFix" @error="diyGoods.error(tagItem,'icon')"></image>
<view class="base-tag" v-else-if="tagItem.style_type == 'diy' || !tagItem.icon" :style="diyGoods.baseTagStyle(tagItem)">
{{tagItem.label_name}}
</view>
</template>
</view>
<view class="flex justify-between flex-wrap items-center mt-[20rpx]">
<view class="flex flex-col">
<view class="flex items-baseline leading-[1]" v-if="diyComponent.priceStyle.control">
<view class="text-[var(--price-text-color)] price-font block truncate max-w-[270rpx]" :style="{ color : diyComponent.priceStyle.color }">
<text class="text-[24rpx] font-400"></text>
<text class="text-[40rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="text-[24rpx] font-500">.{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<image v-if="diyGoods.priceType(item) == 'member_price'" class="max-w-[50rpx] h-[28rpx] ml-[6rpx]" :src="img('addon/shop/VIP.png')" mode="heightFix" />
</view>
<text v-if="diyComponent.saleStyle.control" class="text-[22rpx] mt-[8rpx] text-[var(--text-color-light9)]" :style="{ color : diyComponent.saleStyle.color }">已售{{item.sale_num}}{{item.unit || ''}}</text>
</view>
<view class="absolute right-[16rpx] bottom-[16rpx]" v-if="diyComponent.btnStyle.control">
<view v-if="diyComponent.btnStyle.style == 'button'" :style="goodsBtnCss" class="px-[18rpx] h-[48rpx] flex items-center justify-center">
<text class="text-[20rpx]">{{diyComponent.btnStyle.text}}</text>
</view>
<view v-else :style="goodsBtnCss" class="w-[46rpx] h-[46rpx] rounded-[50%] flex items-center justify-center">
<text :class="[diyComponent.btnStyle.style]" class="nc-iconfont text-[30rpx]"></text>
</view>
</view>
</view>
</view>
</view>
</template>
</view>
<view>
<template v-for="(item,index) in goodsList">
<view v-if="(index%2) == 1" class="flex flex-col bg-[#fff] box-border overflow-hidden" :class="{'mt-[24rpx]': index > 1}" :style="itemCss" @click="toLink(item)">
<u--image :width="style2Width" :height="style2Width" :src="img(item.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image :style="{'width': style2Width,'height': style2Width}" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="relative min-h-[44rpx] px-[16rpx] flex-1 pt-[16rpx] pb-[20rpx] flex flex-col justify-between">
<view class="text-[#303133] leading-[40rpx] text-[28rpx] multi-hidden" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }" v-if="diyComponent.goodsNameStyle.control">
<view class="brand-tag" v-if="item.goods_brand">
{{item.goods_brand.brand_name}}
</view>
{{item.goods_name}}
</view>
<view v-if="item.goods_label_name && item.goods_label_name.length && diyComponent.labelStyle.control" class="flex flex-wrap">
<template v-for="(tagItem, tagIndex) in item.goods_label_name">
<image class="img-tag" v-if="tagItem.style_type == 'icon' && tagItem.icon" :src="img(tagItem.icon)" mode="heightFix" @error="diyGoods.error(tagItem,'icon')"></image>
<view class="base-tag" v-else-if="tagItem.style_type == 'diy' || !tagItem.icon" :style="diyGoods.baseTagStyle(tagItem)">
{{tagItem.label_name}}
</view>
</template>
</view>
<view class="flex justify-between flex-wrap items-center mt-[20rpx]">
<view class="flex flex-col">
<view class="flex items-baseline leading-[1]" v-if="diyComponent.priceStyle.control">
<view class="text-[var(--price-text-color)] price-font block truncate max-w-[270rpx]" :style="{ color : diyComponent.priceStyle.color }">
<text class="text-[24rpx] font-400"></text>
<text class="text-[40rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="text-[24rpx] font-500">.{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2).split('.')[1] }}</text>
</view>
<image v-if="diyGoods.priceType(item) == 'member_price'" class="max-w-[50rpx] h-[28rpx] ml-[6rpx]" :src="img('addon/shop/VIP.png')" mode="heightFix" />
</view>
<text v-if="diyComponent.saleStyle.control" class="text-[22rpx] mt-[8rpx] text-[var(--text-color-light9)]" :style="{ color : diyComponent.saleStyle.color }">已售{{item.sale_num}}{{item.unit || ''}}</text>
</view>
<view class="absolute right-[16rpx] bottom-[16rpx]" v-if="diyComponent.btnStyle.control">
<view v-if="diyComponent.btnStyle.style == 'button'" :style="goodsBtnCss" class="px-[18rpx] h-[48rpx] flex items-center justify-center">
<text class="text-[20rpx]">{{diyComponent.btnStyle.text}}</text>
</view>
<view v-else :style="goodsBtnCss" class="w-[46rpx] h-[46rpx] rounded-[50%] flex items-center justify-center">
<text :class="[diyComponent.btnStyle.style]" class="nc-iconfont text-[30rpx]"></text>
</view>
</view>
</view>
</view>
</view>
</template>
</view>
</block>
<block v-if="diyComponent.style == 'style-3'">
<view :style="style3Css" v-if="goodsList.length">
<scroll-view :id="'warpStyle3-'+diyComponent.id" class="whitespace-nowrap min-h-[290rpx]" :scroll-x="true">
<view :id="'item'+index+diyComponent.id" class="w-[214rpx] mb-[6rpx] inline-block bg-[#fff] box-border overflow-hidden" :class="{'!mr-[0rpx]' : index == (goodsList.length-1)}" :style="itemCss+itemStyle3" v-for="(item,index) in goodsList" :key="item.goods_id" @click="toLink(item)">
<u--image width="214rpx" height="160rpx" :src="img(item.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="w-[214rpx] h-[160rpx]" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="relative min-h-[40rpx] px-[10rpx] pt-[16rpx] pb-[10rpx]">
<view class="text-[26rpx] text-[#303133] truncate" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }" v-if="diyComponent.goodsNameStyle.control">{{item.goods_name}}</view>
<view class="text-[var(--price-text-color)] pt-[16rpx] pb-[6rpx] font-bold price-font block truncate max-w-[160rpx] leading-[1] overflow-hidden" :style="{ color : diyComponent.priceStyle.color }" v-if="diyComponent.priceStyle.control">
<text class="text-[20rpx] font-400 mr-[2rpx]"></text>
<text class="text-[36rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(item)).toFixed(2)}}</text>
</view>
<view v-if="diyComponent.btnStyle.control" class="absolute right-[10rpx] bottom-[12rpx]">
<view v-if="diyComponent.btnStyle.style != 'button'" :style="goodsBtnCss" class="w-[40rpx] h-[40rpx] rounded-[50%] flex items-center justify-center">
<text :class="[diyComponent.btnStyle.style]" class="nc-iconfont text-[28rpx]"></text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</block>
</view>
</view>
</x-skeleton>
</template>
<script setup lang="ts">
//
import { ref,reactive,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { redirect, img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { getGoodsComponents } from '@/addon/shop/api/goods';
import {useGoods} from '@/addon/shop/hooks/useGoods'
const diyGoods = useGoods();
const props = defineProps(['component', 'index','value']);
const diyStore = useDiyStore();
const emits = defineEmits(['loadingFn']); //
const skeleton = reactive({
type: '',
loading: diyStore.mode == 'decorate' ? false : true,
config: {}
})
const goodsList = ref<Array<any>>([]);
const diyComponent = computed(() => {
if(props.value) {
return props.value;
}else if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + (diyComponent.value.componentStartBgColor || diyComponent.value.componentEndBgColor) + ';';
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
const itemCss = computed(() => {
var style = '';
if (diyComponent.value.elementBgColor) style += 'background-color:' + diyComponent.value.elementBgColor + ';';
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if(diyComponent.value.style == 'style-2'){
if(diyComponent.value.margin && diyComponent.value.margin.both) style += 'width: calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 20rpx) / 2);'
else style += 'width: calc((100vw - 20rpx) / 2 );'
}
return style;
})
const goodsBtnCss = computed(() => {
var style = '';
if (diyComponent.value.btnStyle.style == 'button' && diyComponent.value.btnStyle.aroundRadius) style += 'border-radius:' + diyComponent.value.btnStyle.aroundRadius * 2 + 'rpx;';
if (diyComponent.value.btnStyle.startBgColor && diyComponent.value.btnStyle.endBgColor){
style += `background:linear-gradient(${diyComponent.value.btnStyle.startBgColor},${diyComponent.value.btnStyle.endBgColor});`;
}else{
style += 'background-color:' + (diyComponent.value.btnStyle.startBgColor || diyComponent.value.btnStyle.endBgColor) + ';';
}
if (diyComponent.value.btnStyle.textColor) style += 'color:' + diyComponent.value.btnStyle.textColor + ';';
if (diyComponent.value.btnStyle.style == 'button' && diyComponent.value.btnStyle.fontWeight) style += 'font-weight: bold;';
return style;
})
const style2Width = computed(() => {
var style = '';
if(diyComponent.value.margin && diyComponent.value.margin.both) style += 'calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 20rpx) / 2)'
else style += 'calc((100vw - 20rpx) / 2 )'
return style;
})
const style3Css = computed(() => {
var style = '';
style += 'padding:0 20rpx;';
if (diyComponent.value.margin && diyComponent.value.margin.both){style += 'width: calc(100vw - ' + ((diyComponent.value.margin.both * 4) + 40) + 'rpx);'}
else{style += 'box-sizing: border-box; width: 100vw;';}
return style;
})
//
const itemStyle3 = ref('');
const setItemStyle3 = ()=>{
// #ifdef MP-WEIXIN
uni.createSelectorQuery().in(instance).select('#warpStyle3-'+diyComponent.value.id).boundingClientRect((res:any) => {
uni.createSelectorQuery().in(instance).select('#item0'+diyComponent.value.id).boundingClientRect((data:any) => {
itemStyle3.value = `margin-right:${(res.width - data.width*3)/2}px;`
}).exec()
}).exec()
// #endif
// #ifdef H5
itemStyle3.value= 'margin-right:14rpx;'
// #endif
}
const getGoodsListFn = () => {
let data = {
num: (diyComponent.value.source == 'all' || diyComponent.value.source == 'category') ? diyComponent.value.num : '',
goods_ids: diyComponent.value.source == 'custom' ? diyComponent.value.goods_ids : '',
goods_category: diyComponent.value.source == 'category' ? diyComponent.value.goods_category : '',
order: diyComponent.value.sortWay
}
getGoodsComponents(data).then((res) => {
goodsList.value = res.data;
//
// if(goodsList.value.length == 0 && diyComponent.value.pageStyle) {
// diyComponent.value.pageStyle = '';
// }
skeleton.loading = false;
emits('loadingFn', res.data)
if(diyComponent.value.componentBgUrl) {
setTimeout(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-shop-goods-list').boundingClientRect((data: any) => {
if(data) height.value = data.height;
}).exec();
}, 1000)
}
nextTick(()=>{
setTimeout(()=>{
if(diyComponent.value.style == 'style-3') setItemStyle3()
},500)
})
});
}
const initSkeleton = ()=> {
if (diyComponent.value.style == 'style-1') {
//
skeleton.type = 'list'
skeleton.type = 'list'
skeleton.config = {
textRows: 2
};
} else if (diyComponent.value.style == 'style-2') {
//
skeleton.type = 'waterfall'
skeleton.config = {
headHeight: '320rpx',
gridRows: 1,
textRows: 2,
textWidth: ['100%', '80%']
};
} else if (diyComponent.value.style == 'style-3') {
//
skeleton.type = 'waterfall'
skeleton.config = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
}
const instance = getCurrentInstance();
const height = ref(0)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'GoodsList') {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-shop-goods-list').boundingClientRect((data: any) => {
if(data) height.value = data.height;
}).exec();
if(diyComponent.value.style == 'style-3') setItemStyle3()
})
}
}
)
}else{
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},
{deep: true}
)
}
});
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
let obj = {
goods_cover_thumb_mid: "",
goods_name: "商品名称",
sale_num: "100",
unit: "件",
goodsSku:{price:100}
};
goodsList.value.push(obj);
goodsList.value.push(obj);
nextTick(()=>{
if(diyComponent.value.style == 'style-3') setItemStyle3()
})
}else{
initSkeleton();
getGoodsListFn();
}
}
const toLink = (data: any) => {
redirect({ url: '/addon/shop/pages/goods/detail', param: { goods_id: data.goods_id } })
}
</script>
<style lang="scss" scoped>
@import '@/addon/shop/styles/common.scss';
.biserial-goods-list{
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
</style>

340
uni-app/src/addon/shop/components/diy/many-goods-list/index.vue

@ -0,0 +1,340 @@
<template>
<view class="overflow-hidden">
<scroll-view scroll-x="true" class="many-goods-list-head" :class="diyComponent.headStyle" :scroll-into-view="'a' + cateIndex" :style="warpCss">
<template v-if="diyComponent.headStyle == 'style-3'">
<template v-if="diyComponent.source == 'custom'">
<view v-for="(item,index) in diyComponent.list" :key="index" :class="['flex-col inline-flex items-center justify-center', { 'pr-[40rpx]': (index != diyComponent.list.length-1) }]" @click="changeCateIndex(item,index)">
<image :style="{ borderRadius : (diyComponent.aroundRadius * 2) + 'rpx' }" :class="['w-[90rpx] h-[90rpx] overflow-hidden border-[2rpx] border-solid border-transparent', {'border-[var(--primary-color)]': index == cateIndex }]" v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFit"/>
<image :style="{ borderRadius : (diyComponent.aroundRadius * 2) + 'rpx' }" :class="['w-[90rpx] h-[90rpx] overflow-hidden border-[2rpx] border-solid border-transparent', {'border-[var(--primary-color)]': index == cateIndex }]" v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" />
<text :class="['text-[28rpx] mt-[16rpx]', {'font-500 text-[var(--primary-color)]' : index == cateIndex }]">{{ item.title }}</text>
</view>
</template>
<template v-else-if="diyComponent.source == 'goods_category'">
<view class="pr-[40rpx] inline-flex flex-col items-center justify-center" @click="changeGoodsCategory({ category_id : 0 })">
<image :style="{ borderRadius : (diyComponent.aroundRadius * 2) + 'rpx' }" :class="['w-[90rpx] h-[90rpx] overflow-hidden border-[2rpx] border-solid border-transparent', {'border-[var(--primary-color)]': currentCategoryId == 0}]" :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" />
<text :class="['text-[28rpx] mt-[16rpx]', {'font-500 text-[var(--primary-color)]': currentCategoryId == 0}]">全部</text>
</view>
<view v-for="(item,index) in goodsCategoryListData" :key="index" :class="['flex-col inline-flex items-center justify-center', { 'pr-[40rpx]': (index != goodsCategoryListData.length-1) }]" @click="changeGoodsCategory(item)">
<image :style="{ borderRadius : (diyComponent.aroundRadius * 2) + 'rpx' }" :class="['w-[90rpx] h-[90rpx] overflow-hidden border-[2rpx] border-solid border-transparent', {'border-[var(--primary-color)]': currentCategoryId == item.category_id}]" v-if="item.image" :src="img(item.image)" mode="aspectFit"/>
<image :style="{ borderRadius : (diyComponent.aroundRadius * 2) + 'rpx' }" :class="['w-[90rpx] h-[90rpx] overflow-hidden border-[2rpx] border-solid border-transparent', {'border-[var(--primary-color)]': currentCategoryId == item.category_id}]" v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" />
<text :class="['text-[28rpx] mt-[16rpx]', {'font-500 text-[var(--primary-color)]' : currentCategoryId == item.category_id}]">{{ item.category_name }}</text>
</view>
</template>
</template>
<template v-else>
<view v-for="(item, index) in diyComponent.list" class="scroll-item" :class="[diyComponent.headStyle, { active: index == cateIndex }]" :id="'a' + index" :key="index" @click="changeCateIndex(item, index)">
<view class="cate" v-if="diyComponent.headStyle == 'style-1'">
<view class="name">{{ item.title }}</view>
<view class="desc" :v-if="item.desc">{{ item.desc }}</view>
</view>
<view v-if="diyComponent.headStyle == 'style-2'" class="cate">
<view class="name">{{ item.title }}</view>
<text class="nc-iconfont nc-icon-xiaolian-2 !text-[40rpx] text-[var(--primary-color)] transform" v-if="index == cateIndex"></text>
</view>
<view v-if="diyComponent.headStyle == 'style-4'" class="cate">
<view class="name">{{ item.title }}</view>
</view>
</view>
</template>
</scroll-view>
<diy-goods-list class="many-goods-list-body" v-if="goodsValue" :value="goodsValue"></diy-goods-list>
</view>
</template>
<script setup lang="ts">
//
import { ref, computed, watch, onMounted } from 'vue';
import { img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import diyGoodsList from '@/addon/shop/components/diy/goods-list/index.vue';
import { getGoodsCategoryList } from '@/addon/shop/api/goods';
const props = defineProps(['component', 'index']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'ManyGoodsList') {
refresh();
}
}
)
}
});
const refresh = () => {
//
if(diyComponent.value.headStyle == 'style-3' && diyComponent.value.source == 'goods_category' && diyComponent.value.goods_category){
getGoodsCategoryFn(diyComponent.value.goods_category);
}else{
changeCateIndex(diyComponent.value.list[0], 0,true)
}
}
const cateIndex = ref(0) // id
const goodsValue:any = ref(null);
const changeCateIndex = (item:any, index:any, isFirst:any = false)=> {
//
if (diyStore.mode == 'decorate' && !isFirst) return;
cateIndex.value = index;
refreshGoodsList({
source: item.source,
goods_category: item.goods_category,
goods_ids: item.goods_ids
})
}
const goodsCategoryListData = ref([])
const currentCategoryId = ref(0) // id
//
const getGoodsCategoryFn = (pid:any)=> {
getGoodsCategoryList({
pid
}).then(res => {
if (res.code == 1) {
goodsCategoryListData.value = res.data;
if (goodsCategoryListData.value) {
refreshGoodsList({
source: 'category',
goods_category: ''
});
}
}
})
}
//
const changeGoodsCategory = (item :any)=> {
//
if (diyStore.mode == 'decorate') return;
currentCategoryId.value = item.category_id
refreshGoodsList({
source: 'category',
goods_category: currentCategoryId.value
});
}
const refreshGoodsList = (obj:any)=> {
goodsValue.value = {
style: diyComponent.value.style,
margin: diyComponent.value.margin,
source: obj.source,
num: diyComponent.value.num,
sortWay: diyComponent.value.sortWay,
goodsNameStyle: diyComponent.value.goodsNameStyle,
priceStyle: diyComponent.value.priceStyle,
saleStyle: diyComponent.value.saleStyle,
btnStyle: diyComponent.value.btnStyle,
labelStyle: diyComponent.value.labelStyle,
elementBgColor: diyComponent.value.elementBgColor,
topElementRounded: diyComponent.value.topElementRounded,
bottomElementRounded: diyComponent.value.bottomElementRounded
};
if (obj.goods_category) {
goodsValue.value.goods_category = obj.goods_category
}
if (obj.goods_ids && obj.goods_ids.length) {
goodsValue.value.goods_ids = obj.goods_ids
}
}
</script>
<style lang="scss" scoped>
.many-goods-list-head {
left: 0;
right: 0;
z-index: 5;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
padding: 20rpx 0 10rpx;
margin-bottom:20rpx;
position: relative;
&.style-1{
padding: 26rpx 0 14rpx;
}
&.style-2{
height: 100rpx;
padding: 20rpx 0 16rpx;
}
&.style-3{
padding: 26rpx 20rpx;
background-color: #fff;
margin-bottom: 20rpx;
width: 100%;
white-space: nowrap;
box-sizing: border-box;
}
&.style-4{
padding-bottom: 0;
}
.scroll-item {
display: inline-block;
text-align: center;
width: auto;
padding: 0 20rpx;
&.style-1{
&:first-child {
width: calc(25% - 20rpx);
padding-left: 0;
}
&.active {
.name{
color: var(--primary-color);
font-weight: 500;
}
.desc {
color: #ffffff;
border-radius: 20rpx;
background-color: var(--primary-color);
}
}
.name {
font-size: 30rpx;
color: #333;
line-height: 1;
}
.cate {
display: inline-block;
}
.desc {
font-size: 22rpx;
color: #999;
height: 38rpx;
line-height: 38rpx;
display: flex;
align-items: center;
justify-content: center;
margin-top: 10rpx;
min-width: 110rpx;
max-width: 220rpx;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10rpx;
}
}
&.style-2{
.cate{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.name{
font-size: 28rpx;
color: #333;
line-height: 32rpx;
}
&.active {
.name{
color:var(--primary-color);
font-weight: 500;
}
.nc-iconfont{
position: absolute;
bottom: -35rpx;
}
}
}
&.style-4{
padding: 0 10rpx 14rpx;
.cate{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: 10rpx 12rpx;
background-color: #F6F6F6;
border-radius: 12rpx;
min-width: 136rpx;
box-sizing: border-box;
border: 2rpx solid #F6F6F6;
}
.name{
font-size: 28rpx;
color: #333;
line-height: 32rpx;
}
&.active {
.cate{
background-color: var(--primary-color-light);
border-color: var(--primary-color);
position: relative;
&::after{
content: "";
width: 18rpx;
height: 18rpx;
position: absolute;
background-color: var(--primary-color-light);
border: 2rpx solid transparent;
border-bottom-color: var(--primary-color);
border-right-color: var(--primary-color);
bottom: -12rpx;
left: 50%;
transform: translateX(-50%) rotate(45deg);
border-bottom-right-radius: 10rpx;
}
}
.name{
color: var(--primary-color);
font-weight: 500;
}
.nc-iconfont{
position: absolute;
bottom: -35rpx;
}
}
}
}
}
</style>

234
uni-app/src/addon/shop/components/diy/shop-exchange-goods/index.vue

@ -0,0 +1,234 @@
<template>
<x-skeleton :type="skeleton.type" :loading="skeleton.loading" :config="skeleton.config">
<view class="overflow-hidden" :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-shop-exchange-goods-list relative flex flex-wrap justify-between" v-if="goodsList.length">
<view class="overflow-hidden bg-[#fff] flex flex-col box-border w-[calc(50%-10rpx)] rounded-[var(--rounded-mid)]" :class="{'mt-[20rpx]': index > 1}" :style="itemCss" v-for="(item,index) in goodsList" :key="item.goods_id" @click="toLink(item)">
<u--image :width="style2Width" :height="style2Width" radius="var(--rounded-mid)" :src="img(item.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="rounded-[var(--rounded-mid)] overflow-hidden" :style="{'width': style2Width,'height': style2Width}" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="flex-1 pt-[10rpx] pb-[20rpx] px-[16rpx] flex flex-col justify-between">
<view class="text-[#333] leading-[40rpx] text-[28rpx] multi-hidden" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }">{{item.names}}</view>
<view class="text-[22rpx] leading-[28rpx] mt-[10rpx] text-[var(--text-color-light9)]" :style="{ color : diyComponent.saleStyle.color }">已兑{{item.total_exchange_num}}</view>
<view class="flex justify-between flex-wrap items-center mt-[16rpx]" >
<view class="flex flex-col">
<view :style="{ color : diyComponent.priceStyle.mainColor }" class="text-[var(--price-text-color)] price-font ml-[2rpx] flex items-baseline">
<text class="text-[32rpx]">{{ item.point }}</text>
<text class="text-[24rpx] ml-[4rpx]">积分</text>
</view>
<view v-if="item.price&&item.price>0" class="flex items-center mt-[6rpx] price-font">
<text :style="{ color : diyComponent.priceStyle.mainColor }" class="text-[#333] font-400 text-[32rpx]">+{{ parseFloat(item.price).toFixed(2) }}</text>
<text :style="{ color : diyComponent.priceStyle.mainColor }" class="text-[var(--price-text-color)] font-400 ml-[4rpx] text-[24rpx]"></text>
</view>
</view>
<view class="w-[120rpx] h-[54rpx] text-[22rpx] flex-center !text-[#fff] m-0 rounded-full primary-btn-bg remove-border text-center" shape="circle">去兑换</view>
</view>
</view>
</view>
</view>
<view v-else-if="!goodsList.length" class="empty-page">
<image class="img" :src="img('static/resource/images/system/empty.png')" model="aspectFit" />
<view class="desc">暂无商品</view>
</view>
</view>
</x-skeleton>
</template>
<script setup lang="ts">
//
import { ref,reactive,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { redirect, img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { getExchangeComponentsList } from '@/addon/shop/api/point';
const props = defineProps(['component', 'index','value']);
const diyStore = useDiyStore();
const skeleton = reactive({
type: '',
loading: diyStore.mode == 'decorate' ? false : true,
config: {}
})
const goodsList = ref<Array<any>>([]);
const diyComponent = computed(() => {
if(props.value) {
return props.value;
}else if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
const itemCss = computed(() => {
var style = '';
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
return style;
})
const style2Width = computed(() => {
var style = '';
if(diyComponent.value.margin && diyComponent.value.margin.both) style += 'calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 20rpx) / 2)'
else style += 'calc((100vw - 20rpx) / 2 )'
return style;
})
const getGoodsListFn = () => {
let data = {
order: diyComponent.value.sortWay,
num: diyComponent.value.source == 'all' ? diyComponent.value.num : '',
ids: diyComponent.value.source == 'custom' ? diyComponent.value.goods_ids.join(',') : '',
}
getExchangeComponentsList(data).then((res) => {
goodsList.value = res.data;
skeleton.loading = false;
//
// if(goodsList.value.length == 0) {
// diyComponent.value.pageStyle = '';
// }
if(diyComponent.value.componentBgUrl) {
setTimeout(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-shop-exchange-goods-list').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
}, 1000)
}
});
}
const initSkeleton = ()=> {
if (diyComponent.value.style == 'style-1') {
//
skeleton.type = 'list'
skeleton.type = 'list'
skeleton.config = {
textRows: 2
};
} else if (diyComponent.value.style == 'style-2') {
//
skeleton.type = 'waterfall'
skeleton.config = {
headHeight: '320rpx',
gridRows: 1,
textRows: 2,
textWidth: ['100%', '80%']
};
} else if (diyComponent.value.style == 'style-3') {
//
skeleton.type = 'waterfall'
skeleton.config = {
gridRows: 1,
gridColumns: 3,
headHeight: '200rpx',
textRows: 2,
textWidth: ['100%', '80%']
};
}
}
const instance = getCurrentInstance();
const height = ref(0)
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'ShopExchangeGoods') {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-shop-exchange-goods-list').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
}
)
}else{
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},
{deep: true}
)
}
});
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
let obj = {
image: "",
names: "商品名称",
total_exchange_num: 100,
point: 100,
price:100
};
goodsList.value.push(obj);
goodsList.value.push(obj);
}else{
initSkeleton();
getGoodsListFn();
}
}
const toLink = (data: any) => {
redirect({ url: '/addon/shop/pages/point/detail', param: { id: data.id } })
}
</script>
<style lang="scss" scoped>
</style>

125
uni-app/src/addon/shop/components/diy/shop-exchange-info/index.vue

@ -0,0 +1,125 @@
<template>
<view :style="warpCss" class="shop-exchange-info-wrap">
<view class="pl-[60rpx] pt-[71rpx] pb-[133rpx] min-h-[382rpx] box-border text-[#fff]">
<!-- #ifdef MP-WEIXIN -->
<view :style="navbarInnerStyle"></view>
<!-- #endif -->
<template v-if="token">
<view class="text-[34rpx] leading-[48rpx]">我的积分</view>
<view class="text-[80rpx] font-500 price-font leading-[112rpx]">{{memberPoint.point||0}}</view>
<view class="text-[24rpx] font-400 leading-[34rpx]">今日获得{{memberPoint.today_point||0}}</view>
</template>
<template v-else>
<view class="pt-[42rpx] title">积分当钱花</view>
<view class="text-[26rpx] leading-[36rpx] text-[#FEF2C0] mt-[10rpx]">做任务可领积分</view>
</template>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed, ref, watch,onMounted } from 'vue'
import { getExchangePoint } from '@/addon/shop/api/point';
import { img, getToken } from '@/utils/common';
import { t } from '@/locale'
import useDiyStore from '@/app/stores/diy'
const props = defineProps(['component', 'index','global']);
const diyStore = useDiyStore();
const loading = ref(true)
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if (diyComponent.value.bgUrl) {
style += 'background-image:url(' + img(diyComponent.value.bgUrl) + ');';
style += 'background-size: 100%;';
style += 'backgroundPosition: bottom;';
style += 'background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
onMounted(() => {
refresh();
});
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},{deep: true})
const memberPoint :Record<string, any> = ref({})
const token = ref(getToken())
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
memberPoint.value = {
point: 10500,
today_point: 500,
}
} else {
if (!token) {
return;
}
getExchangePointFn()
}
}
const getExchangePointFn= async()=> {
const res = await getExchangePoint()
memberPoint.value = res.data
loading.value = false
}
let menuButtonInfo: any = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
//
const navbarInnerStyle = computed(() => {
let style = '';
//
// #ifdef MP
if (props.global.topStatusBar.isShow == false) {
style += 'height:' + menuButtonInfo.height + 'px;';
style += 'padding-top:' + menuButtonInfo.top + 'px;';
}
// #endif
return style;
})
</script>
<style lang="scss" scoped>
.title{
width: 240rpx;
height: 58rpx;
font-family: FZLanTingHei-EB-GBK, FZLanTingHei-EB-GBK;
font-weight: 700;
font-size: 48rpx;
line-height: 56rpx;
text-align: left;
color: transparent;
background: linear-gradient(90.00002732075491deg, #FFFDF0 5%, #FEF1B9 88%);
-webkit-background-clip: text; /* 确保背景色可以应用到文字上 */
background-clip: text;
}
</style>

190
uni-app/src/addon/shop/components/diy/shop-goods-ranking/index.vue

@ -0,0 +1,190 @@
<template>
<view :style="warpCss" class="overflow-hidden">
<scroll-view scroll-x="true" class="w-[100%] whitespace-nowrap">
<view class="flex items-start">
<view class="inline-block" v-for="(item,index) in diyComponent.list" :key="index">
<view class="w-[460rpx] px-[20rpx] pb-[20rpx] pt-[20rpx] flex flex-col items-start" :class="{'mr-[20rpx]': (diyComponent.list.length-1) != index}" :style="swiperItemCss(item)" v-if="listGoods[item.rankIds[0]] && listGoods[item.rankIds[0]].length > 0">
<view class="flex items-center h-[50rpx]">
<image class="w-[30rpx] h-[30rpx] mr-[10rpx]" v-if="item.imgUrl" :src="img(item.imgUrl)" mode="aspectFill"></image>
<view :style="{'color': item.textColor}">
<text class="text-[30rpx] font-bold" v-if="item.text">{{ item.text }}</text>
<text class="text-[30rpx] font-bold" v-else>{{ listGoodsNames[item.rankIds[0]] }}</text>
</view>
</view>
<view class="flex items-center mt-[6rpx]" :style="{'color': item.subTitle.textColor}" @click="diyStore.toRedirect(item.subTitle.link)">
<text class="text-[24rpx] font-500" v-if="item.subTitle.text">{{item.subTitle.text}}</text>
<text class="iconfont iconyouV6xx !text-[24rpx]" v-if="item.subTitle.text"></text>
</view>
<view class="mt-[24rpx]">
<view class="flex bg-[rgba(255,255,255,0.94)] p-[10rpx] rounded-[16rpx] mb-[16rpx]" v-for="(goods, gIndex) in listGoods[item.rankIds[0]]" :class="{'mb-0': gIndex === listGoods[item.rankIds[0]].length - 1}" @click="toDetail(goods.goods_id)">
<view class="relative w-[130rpx] h-[130rpx] mr-[16rpx]">
<image class="absolute top-[6rpx] left-[8rpx] w-[30rpx] h-[36rpx]" :style="{ zIndex:2 }" :src="getRankBadge(goods.rank_num)" mode="aspectFill"></image>
<view class="absolute top-[2rpx] left-[-3rpx] flex items-center justify-center w-[50rpx] h-[50rpx]" :style="{ zIndex: 3 }">
<text class="text-[20rpx] font-bold text-[#fff]">{{ goods.rank_num }}</text>
</view>
<u--image radius="var(--goods-rounded-big)" width="130rpx" height="130rpx" :src="img(goods.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="w-[130rpx] h-[130rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="flex flex-col">
<view class="leading-[1.3] multi-hidden w-[290rpx] text-[28rpx] whitespace-normal">{{goods.goods_name}}</view>
<view class="flex items-center justify-between mt-[auto]">
<view class="text-[var(--price-text-color)] price-font flex items-baseline">
<text class="text-[24rpx] font-500"></text>
<text class="text-[40rpx] font-500">{{ diyGoods.goodsPrice(goods).toFixed(2).split('.')[0] }}</text>
<text class="text-[24rpx] font-500">.{{ diyGoods.goodsPrice(goods).toFixed(2).split('.')[1] }}</text>
</view>
<view>
<text class="iconfont icongouwuche3 text-[var(--primary-color)] border-[2rpx] border-solid border-[var(--primary-color)] rounded-[50%] text-[22rpx] p-[6rpx]"></text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
//
import { ref,reactive,computed, watch, onMounted } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { redirect, img } from '@/utils/common';
import { getRankComponentsGoodsList } from '@/addon/shop/api/rank'
import { useGoods } from '@/addon/shop/hooks/useGoods'
const props = defineProps(['component', 'index']);
const diyGoods = useGoods();
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const swiperItemCss = (data: any)=> {
var style = '';
if (data.listFrame.startColor) {
if (data.listFrame.startColor && data.listFrame.endColor) style += 'background: linear-gradient(90deg, ' + data.listFrame.startColor + ', ' + data.listFrame.endColor + ');';
else style = 'background-color:' + data.listFrame.startColor + ';';
}
if (data.bgUrl) {
style += 'background-image:' + 'url(' + img(data.bgUrl) + ');';
style += 'background-size: 100%;';
style += 'background-repeat: no-repeat;';
}
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
return style;
}
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},{deep: true})
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
if(diyComponent.value.componentName == 'ShopGoodsRanking') {
const fakeGoods = {
goods_name: '商品名称',
goods_cover_thumb_mid: '',
goodsSku: {
price: 10
},
rank_num: 0 // 0
};
diyComponent.value.list.forEach(item => {
if (!item.goodsList) {
item.goodsList = [];
}
if (item.goodsList.length === 0) {
const fakeGoodsList = [];
// rank_num
for (let i = 0; i < 3; i++) {
const newItem = { ...fakeGoods, rank_num: i + 1 }; // rank_num 1
fakeGoodsList.push(newItem);
}
listGoods[item.rankIds[0]] = fakeGoodsList;
}
});
}
} else {
getRankGoodsListFn();
}
};
const listGoods = reactive({});
const listGoodsNames = reactive({});
const getRankGoodsListFn = () => {
diyComponent.value.list.forEach((item) => {
const rank_id = Array.isArray(item.rankIds) ? item.rankIds[0] : 0;
const data = {
rank_id: item.source === 'custom' ? rank_id : 0
};
getRankComponentsGoodsList(data).then((res) => {
if (res.data && res.data.goods_list.length > 0) {
listGoods[item.rankIds[0]] = res.data.goods_list;
listGoodsNames[item.rankIds[0]] = res.data.name;
}
}).catch((error) => {
console.error('获取商品数据失败:', error);
});
});
};
function getRankBadge(sort: any) {
switch (sort) {
case 1:
return img('addon/shop/rank/rank_first.png');
case 2:
return img('addon/shop/rank/rank_second.png');
case 3:
return img('addon/shop/rank/rank_third.png');
default:
return img('addon/shop/rank/rank.png');
}
}
const toDetail = (id: string | number) => {
redirect({ url: '/addon/shop/pages/goods/detail', param: { goods_id: id }, mode: 'navigateTo' })
}
onMounted(() => {
refresh();
});
</script>
<style lang="scss" scoped>
</style>

203
uni-app/src/addon/shop/components/diy/shop-goods-recommend/index.vue

@ -0,0 +1,203 @@
<template>
<x-skeleton :type="skeleton.type" :loading="skeleton.loading" :config="skeleton.config">
<view :style="warpCss" v-if="goodsNum">
<view class="w-full">
<scroll-view :id="'warpStyle-'+diyComponent.id" class="whitespace-nowrap h-[341rpx] w-full" :scroll-x="true">
<block v-for="(item,index) in goodsList" :key="index">
<view v-if="item.info" :id="'item'+index+diyComponent.id" class="w-[224rpx] h-[341rpx] mr-[20rpx] inline-block bg-[#fff] box-border overflow-hidden" :class="{'!mr-[0rpx]' : index == (goodsList.length-1)}" :style="itemCss+itemStyle" @click="toLink(item)">
<view class="w-full h-[134rpx]" :style="listFrameStyle(item)">
<view class="flex pl-[16rpx] pr-[20rpx] justify-between h-[63rpx] items-center">
<view class="text-[28rpx] leading-[34rpx] flex-1 mr-[8rpx]" :style="{'color':item.title.textColor}">{{item.title.text}}</view>
<view class="w-[68rpx] h-[34rpx] text-[22rpx] text-center leading-[34rpx] text-[#fff] rounded-[17rpx]" :style="moreTitleStyle(item)">{{item.moreTitle.text}}</view>
</view>
</view>
<view class="mt-[-71rpx] h-[278rpx] w-full px-[20rpx] pt-[18rpx] box-border bg-white" :style="itemCss">
<view class="flex items-center justify-center w-[184rpx] h-[184rpx]">
<u--image width="184rpx" height="184rpx" :radius="'var(--goods-rounded-small)'" :src="img(item.info.goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="w-[184rpx] h-[184rpx] rounded-[var(--goods-rounded-small)]" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="pt-[12rpx]">
<view class="h-[44rpx] pl-[4rpx] min-w-[168rpx] box-border flex justify-between items-center mx-auto border-[2rpx] border-solid border-[var(--primary-color)] rounded-[20rpx]" :style="{'border-color':item.button.color}">
<view class="text-[var(--price-text-color)] font-bold price-font flex items-baseline leading-[40rpx] flex-1 justify-center" >
<view class="leading-[1] max-w-[105rpx] truncate" :style="{ color : diyComponent.priceStyle.mainColor }">
<text class="text-[18rpx] font-400 mr-[2rpx]"></text>
<text class="text-[28rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(item.info.goodsSku)).toFixed(2)}}</text>
</view>
</view>
<view class="w-[70rpx] box-border text-right text-[#fff] pr-[8rpx] text-[22rpx] font-500 leading-[44rpx] rounded-tr-[20rpx] rounded-br-[20rpx] rounded-tl-[24rpx] relative" :style="{'background-color':item.button.color}">
<text>{{item.button.text}}</text>
<image class="w-[24rpx] h-[44rpx] absolute top-[-2rpx] left-0" :src="img('/addon/shop/Union.png')" />
</view>
</view>
</view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</x-skeleton>
</template>
<script setup lang="ts">
//
import { ref,reactive,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { redirect, img, deepClone } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { getGoodsComponents } from '@/addon/shop/api/goods';
import {useGoods} from '@/addon/shop/hooks/useGoods'
const diyGoods = useGoods();
const props = defineProps(['component', 'index','value']);
const diyStore = useDiyStore();
const emits = defineEmits(['loadingFn']); //
const goodsNum = ref(0);
const skeleton = reactive({
type: '',
loading: false,
config: {}
})
const goodsList = ref<Array<any>>([]);
const diyComponent = computed(() => {
if(props.value) {
return props.value;
}else if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const itemCss = computed(() => {
var style = '';
if (diyComponent.value.elementBgColor) style += 'background-color:' + diyComponent.value.elementBgColor + ';';
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
return style;
})
//listFrame
const listFrameStyle = (item:any)=>{
var style= '';
if(item.listFrame.startColor){
if(item.listFrame.startColor && item.listFrame.endColor) style = `background:linear-gradient( 110deg, ${item.listFrame.startColor} 0%, ${item.listFrame.endColor} 100%);`;
else style = 'background-color:' + item.listFrame.startColor + ';';
}
return style
}
//moreTitle
const moreTitleStyle = (item:any)=>{
var style= '';
if(item.moreTitle.startColor){
if(item.moreTitle.startColor && item.moreTitle.endColor) style = `background:linear-gradient( 0deg, ${item.moreTitle.startColor} 0%, ${item.moreTitle.endColor} 100%);`;
else style = 'background-color:' + item.moreTitle.startColor + ';';
}
return style
}
//
const itemStyle = ref('');
const setItemStyle = ()=>{
// #ifdef MP-WEIXIN
// uni.createSelectorQuery().in(instance).select('#warpStyle-'+diyComponent.value.id).boundingClientRect((res:any) => {
// uni.createSelectorQuery().in(instance).select('#item0'+diyComponent.value.id).boundingClientRect((data:any) => {
// itemStyle.value = `margin-right:${(res.width - data.width*3)/2}px;`
// }).exec()
// }).exec()
// #endif
// #ifdef H5
// itemStyle.value= 'margin-right:19rpx;'
// #endif
if(diyComponent.value.margin && diyComponent.value.margin.both) itemStyle.value = 'width: calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 40rpx) / 3);'
else itemStyle.value = 'width: calc((100vw - 40rpx) / 3 );'
}
setItemStyle();
const getGoodsListFn = () => {
let data: any = {}
if(diyComponent.value.source == 'all') {
data.num = diyComponent.value.list.length;
}else if(diyComponent.value.source == 'custom') {
data.goods_ids = diyComponent.value.goods_ids;
}
getGoodsComponents(data).then((res) => {
let goodsObj = res.data;
goodsNum.value = goodsObj.length || 0;
diyComponent.value.list.filter((el:any, index)=>{
el.info=deepClone(goodsObj[index])
});
goodsList.value = deepClone(diyComponent.value.list)
skeleton.loading = false;
});
}
const instance = getCurrentInstance();
onMounted(() => {
refresh();
});
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},
)
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
nextTick(() => {
if(diyComponent.value && diyComponent.value.list){
goodsList.value = diyComponent.value.list.map((el:any)=>{
let obj = deepClone(el)
obj.info={
goods_cover_thumb_mid:'',
goodsSku:{
price:'10.00'
}
}
return obj
})
goodsNum.value = 3;
setItemStyle()
}
})
}else{
getGoodsListFn();
}
}
const toLink = (data: any) => {
redirect({ url: '/addon/shop/pages/goods/detail', param: { goods_id: data.info.goods_id } })
}
</script>
<style lang="scss" scoped>
</style>

262
uni-app/src/addon/shop/components/diy/shop-member-info/index.vue

@ -0,0 +1,262 @@
<template>
<view :style="warpCss">
<view class="px-[30rpx] pt-[30rpx] box-border pb-[30rpx]" :class="{'!pb-[120rpx]':diyComponent.isShowAccount}">
<!-- #ifdef MP-WEIXIN -->
<view :style="navbarInnerStyle"></view>
<!-- #endif -->
<view v-if="info" class="flex items-center">
<!-- 唤起获取微信 -->
<u-avatar :src="img(info.headimg)" :size="'110rpx'" leftIcon="none" :default-url="img('static/resource/images/default_headimg.png')" @click="clickAvatar" />
<view class="ml-[20rpx] flex-1">
<view class="text-[#ffffff] flex items-baseline flex-wrap" :style="{ color : diyComponent.textColor }">
<view class="text-[32rpx] truncate max-w-[320rpx] font-500 leading-[38rpx]">{{ info.nickname }}</view>
<view class="text-[26rpx] font-400 leading-[28rpx] ml-[10rpx]" v-if="info.mobile">{{info.mobile.replace(info.mobile.substring(3,7), "****")}}</view>
<!-- #ifdef H5 -->
<view v-else-if="!info.mobile" @click="bindMobileFn" class="text-[22rpx] ml-[10rpx] px-[6rpx] border-[1rpx] border-solid border-[#E3E4E9] rounded-[8rpx] h-[34rpx] flex-center" :style="diyComponent.textColor ? { boxShadow: '0 0 0 1rpx ' + diyComponent.textColor, border: 'none' } : {}">{{ t('bindMobile') }}</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button v-else-if="!info.mobile" class="text-[22rpx] ml-[10rpx] bg-[#fff] px-[6rpx] border-[1rpx] border-solid border-[#E3E4E9] rounded-[8rpx] h-[37rpx] flex-center mr-0" :style="diyComponent.textColor ? { boxShadow: '0 0 0 1rpx ' + diyComponent.textColor, border: 'none' } : {}" open-type="getPhoneNumber" @getphonenumber="memberStore.bindMobile">{{t('bindMobile')}}</button>
<!-- #endif -->
</view>
<view class="text-[#666] text-[24rpx] font-400 leading-[28rpx] mt-[14rpx]" :style="{ color : diyComponent.uidTextColor }">UID{{ info.member_no }}</view>
</view>
<text @click="redirect({ url: '/app/pages/setting/index' })" class="nc-iconfont nc-icon-shezhiV6xx1 text-[38rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text>
</view>
<view v-else class="flex items-center">
<u-avatar :src="img('static/resource/images/default_headimg.png')" :size="'100rpx'" @click="toLogin" />
<view class="ml-[20rpx] flex-1" @click="toLogin">
<view class="text-[32rpx] font-500 leading-[38rpx]" :style="{ color : diyComponent.textColor }">
{{ t('login') }}/{{ t('register') }}
</view>
</view>
<view @click="redirect({ url: '/app/pages/setting/index' })">
<text class="nc-iconfont nc-icon-shezhiV6xx1 text-[38rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text>
</view>
</view>
<view class="flex mt-[40rpx] items-center" v-if="diyComponent.isShowAccount">
<view class="text-center w-[33.333%] flex-shrink-0">
<view class="text-[36rpx] mb-[20rpx] font-500 price-font">
<view @click="redirect({url: '/app/pages/member/balance'})" :style="{ color : diyComponent.textColor }">{{ money }}</view>
</view>
<view class="text-[22rpx] font-400">
<view @click="redirect({url: '/app/pages/member/balance'})" :style="{ color : '#666' }">{{ t('balance') }}</view>
</view>
</view>
<view class="text-center w-[33.333%] flex-shrink-0">
<view class="text-[36rpx] mb-[20rpx] font-500 price-font">
<view @click="redirect({url: '/app/pages/member/point'})" :style="{ color : diyComponent.textColor }">{{ parseInt(info?.point) || 0 }}</view>
</view>
<view class="text-[22rpx] font-400">
<view @click="redirect({url: '/app/pages/member/point'})" :style="{ color : '#666' }">{{ t('point') }}</view>
</view>
</view>
<view class="text-center w-[33.333%] flex-shrink-0" @click="redirect({ url: '/addon/shop/pages/member/my_coupon' })">
<view class="text-[36rpx] mb-[20rpx] font-500 price-font">
<view :style="{ color : diyComponent.textColor }">{{ couponCount }}</view>
</view>
<view class="text-[22rpx] font-400">
<view :style="{ color : '#666' }">{{ t('coupon') }}</view>
</view>
</view>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<information-filling ref="infoFillRef"></information-filling>
<!-- #endif -->
<!-- 强制绑定手机号 -->
<bind-mobile ref="bindMobileRef" />
</view>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import useMemberStore from '@/stores/member'
import { useLogin } from '@/hooks/useLogin'
import { getMyCouponCount } from '@/addon/shop/api/coupon'
import { img, isWeixinBrowser, redirect, urlDeconstruction, moneyFormat } from '@/utils/common'
import { t } from '@/locale'
import { wechatSync } from '@/app/api/system'
import useDiyStore from '@/app/stores/diy'
import useConfigStore from '@/stores/config'
import bindMobile from '@/components/bind-mobile/bind-mobile.vue';
const props = defineProps(['component', 'index','global']);
const configStore = useConfigStore()
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.bgUrl) {
style += 'background-image:url(' + img(diyComponent.value.bgUrl) + ');';
style += 'background-size: 100%;';
style += 'background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const memberStore = useMemberStore()
// #ifdef H5
const { query } = urlDeconstruction(location.href)
if (query.code && isWeixinBrowser()) {
setTimeout(() =>{
wechatSync({ code: query.code }).then(res => {
memberStore.getMemberInfo()
})
},1500)
}
// #endif
const info = computed(() => {
//
if (diyStore.mode == 'decorate') {
return {
headimg: '',
nickname: '昵称',
member_level_name: '普通会员',
balance: 0,
point: 0,
money: 0,
mobile: '155****0549',
member_no: 'NIU0000021'
}
} else {
getMyCouponCountFn()
return memberStore.info;
}
})
const money = computed(() => {
if (info.value) {
let m = parseFloat(info.value.balance) + parseFloat(info.value.money)
return moneyFormat(m.toString());
} else {
return 0;
}
})
const toLogin = () => {
let normalLogin = !configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile; //
let authRegisterLogin = !configStore.login.is_auth_register; //
// #ifdef H5
if (isWeixinBrowser()) {
//
if (normalLogin && authRegisterLogin) {
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
} else if (configStore.login.is_username || configStore.login.is_mobile || configStore.login.is_bind_mobile) {
useLogin().setLoginBack({ url: '/addon/shop/pages/member/index' })
} else if (normalLogin && configStore.login.is_auth_register && configStore.login.is_force_access_user_info) {
//
useLogin().getAuthCode({ scopes: 'snsapi_userinfo' })
} else if (normalLogin && configStore.login.is_auth_register && !configStore.login.is_force_access_user_info) {
//
useLogin().getAuthCode({ scopes: 'snsapi_base' })
}
} else {
//
if (normalLogin) {
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
} else if (configStore.login.is_username || configStore.login.is_mobile || configStore.login.is_bind_mobile) {
useLogin().setLoginBack({ url: '/addon/shop/pages/member/index' })
}
}
// #endif
// #ifdef MP
if (normalLogin && authRegisterLogin) {
uni.showToast({ title: '商家未开启登录注册', icon: 'none' })
} else if (configStore.login.is_username || configStore.login.is_mobile || configStore.login.is_bind_mobile) {
useLogin().setLoginBack({ url: '/addon/shop/pages/member/index' })
} else if (normalLogin && configStore.login.is_auth_register && !configStore.login.is_force_access_user_info) {
//
useLogin().getAuthCode()
} else if (configStore.login.is_auth_register && configStore.login.is_force_access_user_info) {
//
useLogin().setLoginBack({ url: '/addon/shop/pages/member/index' })
} else if (configStore.login.is_auth_register && configStore.login.is_bind_mobile) {
//
useLogin().setLoginBack({ url: '/addon/shop/pages/member/index' })
}
// #endif
}
const infoFillRef:any = ref(false)
const clickAvatar = () => {
// #ifdef MP-WEIXIN
infoFillRef.value.show = true
// #endif
// #ifdef H5
if (isWeixinBrowser()) {
useLogin().getAuthCode({ scopes: 'snsapi_userinfo' })
} else {
redirect({ url: '/app/pages/member/personal' })
}
// #endif
}
const couponCount = ref(0)
const getMyCouponCountFn= async()=>{
try {
const res = await getMyCouponCount({status:1})
couponCount.value = res.data
} catch (e){
couponCount.value = 0
}
}
//
const bindMobileRef: any = ref(null)
const bindMobileFn = () =>{
bindMobileRef.value.open()
}
let menuButtonInfo: any = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
//
const navbarInnerStyle = computed(() => {
let style = '';
//
// #ifdef MP
if (props.global.topStatusBar.isShow == false) {
style += 'height:' + menuButtonInfo.height + 'px;';
style += 'padding-top:' + menuButtonInfo.top + 'px;';
}
// #endif
return style;
})
</script>
<style lang="scss" scoped>
</style>

476
uni-app/src/addon/shop/components/diy/shop-newcomer/index.vue

@ -0,0 +1,476 @@
<template>
<view class="shop-newcomer overflow-hidden" :style="warpCss" v-if="list && Object.keys(list).length">
<view class="style-1 p-[20rpx]" v-if="diyComponent.style.value == 'style-1'">
<view class="head flex justify-between items-center mb-[16rpx]" @click="toListFn()">
<image v-if="diyComponent.textImg" class="h-[34rpx] w-[auto]" :src="img(diyComponent.textImg)" mode="heightFix"></image>
<view class="time-wrap flex items-center ml-[auto]" v-show="timeData && Object.keys(timeData).length">
<text v-if="!getToken() && diyStore.mode != 'decorate'" class="text-[24rpx] font-500" :style="{color: diyComponent.countDown.otherColor}">活动未开始</text>
<block v-else-if="activeState()">
<text :style="{color: diyComponent.countDown.otherColor}" class="mr-[10rpx] text-[24rpx]">距结束还有</text>
<up-count-down class="text-[#fff] text-[28rpx]" :time="newcomerTime" format="HH:mm:ss" @change="onChange">
<view class="flex">
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="dayTransitionHours()">{{ dayTransitionHours() }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] ml-[6rpx] mr-[7rpx]">:</text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.minutes">{{ timeData.minutes >= 10?timeData.minutes:'0'+timeData.minutes }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] ml-[6rpx] mr-[7rpx]">:</text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.seconds">{{ timeData.seconds<10 ? '0'+timeData.seconds : timeData.seconds}}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
</view>
</view>
</up-count-down>
</block>
<text v-else class="text-[28rpx]" :style="{color: diyComponent.countDown.otherColor}">活动已结束</text>
</view>
</view>
<scroll-view scroll-x="true" class="content">
<view class="flex">
<view class="inline-flex bg-[#fff] p-[16rpx] box-border" :style="commonTempCss()" @click="toDetail(list[0])">
<view class="w-[150rpx] h-[150rpx] flex items-center justify-center">
<u--image radius="var(--goods-rounded-big)" width="150rpx" height="150rpx" :src="img(list[0].sku_image || '')" model="aspectFill">
<template #error>
<image class="w-[150rpx] h-[150rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="flex flex-col ml-[20rpx] py-[4rpx] flex-1">
<view class="text-[26rpx] w-[200rpx] whitespace-pre-wrap leading-[1.4] multi-hidden" v-if="list[0].goods">{{list[0].goods.goods_name}}</view>
<view class="flex items-center justify-between mt-[auto]">
<view class="flex flex-1 items-center">
<text class="text-[20rpx] text-[#FF0000]"></text>
<text class="text-[28rpx] font-500 text-[#FF0000] max-w[120rpx] truncate">{{goodsPrice(list[0])}}</text>
</view>
<text class="italic flex items-center justify-center rounded-[40rpx] w-[60rpx] h-[40rpx] leading-1 text-[#fff] font-bold first-btn-bg"></text>
</view>
</view>
</view>
<block v-for="(item,index) in list" :key="index">
<view v-if="index > 0" class="ml-[10rpx] inline-flex flex-col items-center p-[16rpx] bg-[#fff] box-border" :style="commonTempCss()" @click="toDetail(item)">
<view class="w-[110rpx] h-[110rpx] flex items-center justify-center">
<u--image radius="var(--goods-rounded-big)" width="110rpx" height="110rpx" :src="img(item.sku_image || '')" model="aspectFill">
<template #error>
<image class="w-[110rpx] h-[110rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="flex items-center mt-[auto]">
<text class="text-[24rpx] font-500"></text>
<text class="text-[24rpx] font-500 truncate">{{goodsPrice(item)}}</text>
</view>
</view>
</block>
</view>
</scroll-view>
</view>
<view class="style-2 p-[20rpx]" v-if="diyComponent.style.value == 'style-2'">
<view class="head flex justify-between items-center mb-[16rpx]" @click="toListFn()">
<image v-if="diyComponent.textImg" class="h-[34rpx] w-[auto]" :src="img(diyComponent.textImg)" mode="heightFix"></image>
<view class="time-wrap flex items-center ml-[auto]" v-show="timeData && Object.keys(timeData).length">
<text v-if="!getToken() && diyStore.mode != 'decorate'" class="text-[24rpx] font-500" :style="{color: diyComponent.countDown.otherColor}">活动未开始</text>
<block v-else-if="activeState()">
<text :style="{color: diyComponent.countDown.otherColor}" class="mr-[10rpx] text-[24rpx]">倒计时</text>
<up-count-down class="text-[#fff] text-[28rpx]" :time="newcomerTime" format="DD:HH:mm" @change="onChange">
<view class="flex">
<view class="text-[24rpx] flex items-center">
<text class="time-num" :style="countDownTextCss" v-if="timeData.days">{{ timeData.days }}</text>
<text class="time-num" :style="countDownTextCss" v-else>0</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[22rpx] ml-[6rpx] mr-[7rpx]"></text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num" :style="countDownTextCss" v-if="timeData.hours">{{ timeData.hours>=10?timeData.hours:'0'+timeData.hours}}</text>
<text class="time-num" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[22rpx] ml-[6rpx] mr-[7rpx]"></text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num" :style="countDownTextCss" v-if="timeData.minutes">{{ timeData.minutes >= 10?timeData.minutes:'0'+timeData.minutes }}</text>
<text class="time-num" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[22rpx] ml-[6rpx] mr-[7rpx]"></text>
</view>
</view>
</up-count-down>
</block>
<text v-else class="text-[28rpx]" :style="{color: diyComponent.countDown.otherColor}">活动已结束</text>
</view>
</view>
<scroll-view scroll-x="true" class="content">
<view class="flex">
<view v-for="(item,index) in list" :key="index" class="item-bg mr-[10rpx] inline-flex flex-col items-center p-[6rpx] bg-[#fff] box-border" :style="commonTempCss()" @click="toDetail(item)">
<view class="flex items-center justify-center w-[146rpx] h-[146rpx]">
<u--image radius="var(--goods-rounded-small)" width="146rpx" height="146rpx" :src="img(item.sku_image || '')" model="aspectFill">
<template #error>
<image class="w-[146rpx] h-[146rpx] rounded-[var(--goods-rounded-small)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<image class="h-[32rpx] w-[auto] mt-[12rpx] mb-[8rpx]" :src="img('addon/shop/diy/newcomer/style_2_img.png')" mode="heightFix"></image>
<view class="flex items-center text-[#fff] pb-[4rpx]">
<text class="text-[20rpx] font-500"></text>
<text class="text-[30rpx] max-w-[120rpx] font-500 truncate">{{goodsPrice(item)}}</text>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="style-3 pt-[20rpx] pb-[10rpx] px-[10rpx]" v-if="diyComponent.style.value == 'style-3'">
<view class="head flex mx-[10rpx] items-center mb-[12rpx]" @click="toListFn()">
<image v-if="diyComponent.textImg" class="h-[34rpx] w-[auto] mr-[16rpx]" :src="img(diyComponent.textImg)" mode="heightFix"></image>
<view class="time-wrap flex items-center" v-show="timeData && Object.keys(timeData).length">
<text v-if="!getToken() && diyStore.mode != 'decorate'" :style="{color: diyComponent.countDown.otherColor}" class="text-[24rpx] font-500">活动未开始</text>
<up-count-down v-else-if="activeState()" class="text-[#fff] text-[28rpx]" :time="newcomerTime" format="HH:mm:ss" @change="onChange">
<view class="flex">
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="dayTransitionHours()">{{ dayTransitionHours() }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] font-bold ml-[6rpx] mr-[7rpx]">:</text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.minutes">{{ timeData.minutes >= 10?timeData.minutes:'0'+timeData.minutes }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] font-bold ml-[6rpx] mr-[7rpx]">:</text>
</view>
<view class="text-[24rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.seconds">{{ timeData.seconds<10 ? '0'+timeData.seconds : timeData.seconds}}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
</view>
</view>
</up-count-down>
<text v-else :style="{color: diyComponent.countDown.otherColor}" class="text-[26rpx]">活动已结束</text>
</view>
<view class="ml-[auto] rounded-[20rpx] flex items-baseline pl-[16rpx] pr-[10rpx] pt-[10rpx] pb-[10rpx]" :style="subTitleCss" @click.stop="diyStore.toRedirect(diyComponent.subTitle.link)">
<text class="text-[22rpx]">{{diyComponent.subTitle.text}}</text>
<text class="iconfont iconarrow-right !text-[18rpx] font-bold"></text>
</view>
</view>
<scroll-view scroll-x="true" class="content bg-[#fff] box-border p-[16rpx] rounded-[var(--rounded-small)]">
<view class="flex">
<view v-for="(item,index) in list" :key="index" class="item-bg inline-flex flex-col items-center box-border" :class="{'mr-[16rpx]': index != (list.length-1)}" :style="commonTempCss()" @click="toDetail(item)">
<view class="bg-[#f8f8f8] flex items-center justify-center w-[152rpx] h-[152rpx]">
<u--image radius="var(--goods-rounded-small)" width="152rpx" height="152rpx" :src="img(item.sku_image || '')" model="aspectFill">
<template #error>
<image class="w-[152rpx] h-[152rpx] rounded-[var(--goods-rounded-small)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<image class="h-[32rpx] w-[auto] mt-[12rpx] mb-[10rpx]" :src="img('addon/shop/diy/newcomer/style_3_img.png')" mode="heightFix"></image>
<view class="flex items-center text-[#FF0E00] pb-[2rpx]">
<text class="text-[20rpx] font-500"></text>
<text class="text-[30rpx] max-w-[120rpx] font-500 truncate">{{goodsPrice(item)}}</text>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="style-4 p-[20rpx] pt-[24rpx]" v-if="diyComponent.style.value == 'style-4'" :style="{ background: 'url(' + img('addon/shop/diy/newcomer/style_4_head.png') + ') no-repeat'}">
<view class="head flex mx-[10rpx] items-center justify-between mb-[24rpx]">
<image v-if="diyComponent.textImg" class="h-[34rpx] w-[auto]" :src="img(diyComponent.textImg)" mode="heightFix"></image>
<view class="time-wrap ml-[auto] flex items-center -mt-[8rpx]" v-show="timeData && Object.keys(timeData).length">
<text v-if="!getToken() && diyStore.mode != 'decorate'" :style="{color: diyComponent.countDown.otherColor}" class="w-[200rpx] text-center text-[24rpx] font-500 pb-[4rpx]">活动未开始</text>
<block v-else-if="activeState()">
<text :style="{color: diyComponent.countDown.otherColor}" class="mr-[8rpx] text-[24rpx]">本场仅剩</text>
<up-count-down class="text-[#fff] text-[28rpx]" :time="newcomerTime" format="HH:mm:ss" @change="onChange">
<view class="flex">
<view class="text-[28rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="dayTransitionHours()">{{ dayTransitionHours() }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] ml-[4rpx] font-bold mr-[5rpx]">:</text>
</view>
<view class="text-[28rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.minutes">{{ timeData.minutes >= 10?timeData.minutes:'0'+timeData.minutes }}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
<text :style="{color: diyComponent.countDown.otherColor}" class="text-[20rpx] ml-[4rpx] font-bold mr-[5rpx]">:</text>
</view>
<view class="text-[28rpx] flex items-center">
<text class="time-num font-500" :style="countDownTextCss" v-if="timeData.seconds">{{ timeData.seconds<10 ? '0'+timeData.seconds : timeData.seconds}}</text>
<text class="time-num font-500" :style="countDownTextCss" v-else>00</text>
</view>
</view>
</up-count-down>
</block>
<text v-else :style="{color: diyComponent.countDown.otherColor}" class="w-[200rpx] text-center text-[24rpx] pb-[4rpx]">活动已结束</text>
</view>
</view>
<scroll-view scroll-x="true" class="content">
<view class="flex">
<view v-for="(item,index) in list" :key="index" class="item-bg inline-flex flex-col items-center box-border" :class="{'mr-[20rpx]': index != (list.length-1)}" :style="commonTempCss()" @click="toDetail(item)">
<view class="relative flex items-center justify-center w-[100%] h-[130rpx] pt-[40rpx] mb-[10rpx]">
<u--image radius="var(--goods-rounded-small)" width="130rpx" height="130rpx" :src="img(item.sku_image || '')" model="aspectFill">
<template #error>
<image class="w-[130rpx] h-[130rpx] rounded-[var(--goods-rounded-small)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="content-sign text-[20rpx] text-[#fff]">新人价</view>
</view>
<view class="w-[210rpx] relative -right-[2rpx] -bottom-[2rpx] flex items-center text-[#FF0E00] pb-[2rpx]">
<view class="flex items-center justify-center flex-1">
<text class="text-[20rpx] font-500"></text>
<text class="text-[36rpx] max-w-[140rpx] font-500 truncate">{{goodsPrice(item)}}</text>
</view>
<text class="btn-bg ml-auto"></text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,reactive,computed, onMounted } from 'vue';
import { redirect, img, getToken } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { getNewcomersComponentsList } from '@/addon/shop/api/newcomer'
const props = defineProps(['component', 'index','value']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if(props.value) {
return props.value;
}else if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
/********* 倒计时 - start ***********/
// 使 reactive
const timeData = ref({});
// onChange
const onChange = (e) => {
timeData.value = e;
};
const newcomerTime: any = ref('');
/********* 倒计时 - end ***********/
const goodsPrice = (data: any)=> {
let price: any = 0;
if (data && data.newcomer_price) {
price = Number(data.newcomer_price).toFixed(2);
}
return price;
}
//
const dayTransitionHours = ()=>{
let num = timeData.value.days * 24 + timeData.value.hours;
num = num ? num : 0;
num = num >=10 ? num : '0' + num;
return num;
}
//
const activeState = ()=>{
let bool = true;
if(diyStore.mode != 'decorate' && timeData.value.days <= 0 && timeData.value.hours <= 0 && timeData.value.minutes <= 0 && timeData.value.seconds <= 0 && timeData.value.milliseconds <= 0){
bool = false;
}
return bool;
}
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const countDownTextCss = computed(() => {
var style = '';
if(diyComponent.value.countDown && diyComponent.value.countDown.numberBg) {
if (diyComponent.value.countDown.numberBg.startColor && diyComponent.value.countDown.numberBg.endColor) style += `background:linear-gradient(${diyComponent.value.countDown.numberBg.startColor},${diyComponent.value.countDown.numberBg.endColor});`;
else{
style += 'background-color:' + (diyComponent.value.countDown.numberBg.startColor || diyComponent.value.countDown.numberBg.endColor) + ';';
}
}
if (diyComponent.value.countDown.numberColor) style += 'color:' + diyComponent.value.countDown.numberColor + ';';
return style;
})
//
const commonTempCss = ()=>{
var style = '';
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
return style;
}
//
const subTitleCss = computed(() => {
var style = '';
if(diyComponent.value.subTitle) {
if (diyComponent.value.subTitle.startColor && diyComponent.value.subTitle.endColor) style += `background:linear-gradient(to right, ${diyComponent.value.subTitle.startColor},${diyComponent.value.subTitle.endColor});`;
else{
style += 'background-color:' + (diyComponent.value.subTitle.startColor || diyComponent.value.subTitle.endColor) + ';';
}
}
if (diyComponent.value.subTitle.textColor) style += 'color:' + diyComponent.value.subTitle.textColor + ';';
return style;
})
const list: any = ref([])
const getNewcomerListFn = () => {
let data = {
limit: diyComponent.value.source == 'all' ? diyComponent.value.num : '',
sku_ids: diyComponent.value.source == 'custom' ? diyComponent.value.goods_ids : ''
}
getNewcomersComponentsList(data).then((res: any) => {
newcomerTime.value = res.data.validity_time;
let now = new Date();
let timestamp: any = now.getTime();
newcomerTime.value = Number(newcomerTime.value) * 1000 - timestamp;
list.value = res.data.goods_list
//
// if(!(list.value.length && (newcomerTime.value > 0 && isJoin.value == 0))) {
// diyComponent.value.pageStyle = '';
// }
})
}
onMounted(() => {
//
if (diyStore.mode == 'decorate') {
let obj = {
goods: {
goods_name: "商品名称"
},
sku_image: "",
newcomer_price: 0.01
};
list.value.push(obj);
list.value.push(obj);
list.value.push(obj);
} else {
getNewcomerListFn();
}
});
//
const toListFn = ()=>{
redirect({ url: '/addon/shop/pages/newcomer/list'})
}
//
const toDetail = (data: any)=> {
redirect({ url: '/addon/shop/pages/goods/detail', param: { sku_id: data.sku_id, type: 'newcomer_discount' } })
}
</script>
<style lang="scss" scoped>
.shop-newcomer{
.style-1{
.content{
white-space: nowrap;
}
.first-btn-bg{
background: linear-gradient( 140deg, #FF9C24 0%, #FF4837 100%);
}
.time-num{
display: flex;
align-items: center;
justify-content: center;
min-width: 42rpx;
padding: 0 6rpx;
height: 36rpx;
border-radius: 6rpx;
box-sizing: border-box;
}
}
.style-2{
.content{
white-space: nowrap;
}
.item-bg{
background: linear-gradient( 140deg, #FF7A41, #FF2E0A);
}
.time-num{
display: flex;
align-items: center;
justify-content: center;
min-width: 42rpx;
padding: 0 6rpx;
height: 36rpx;
border-radius: 6rpx;
box-sizing: border-box;
}
}
.style-3{
.content{
white-space: nowrap;
}
.time-num{
display: flex;
align-items: center;
justify-content: center;
min-width: 42rpx;
padding: 0 6rpx;
height: 36rpx;
border-radius: 6rpx;
box-sizing: border-box;
}
}
.style-4{
background-size: 100% 110rpx !important;
.content{
white-space: nowrap;
}
.time-wrap{
margin-right: -14rpx;
.time-num{
display: flex;
align-items: center;
justify-content: center;
min-width: 32rpx;
height: 36rpx;
border-radius: 6rpx;
font-size: 26rpx;
box-sizing: border-box;
}
}
.item-bg{
background: linear-gradient(#FFFFFF 60%, #f7f7f7 100%);
}
.btn-bg{
background: linear-gradient( 140deg, #FE2B2B 0%, #FF7236 100%);
border-radius: 50%;
border-bottom-left-radius: 0;
font-size: 30rpx;
color: #fff;
width: 50rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
}
.content-sign{
position: absolute;
left: 0;
top: 20rpx;
z-index: 1;
background: linear-gradient( 140deg, #FE2B2B 0%, #FF7236 100%);
padding: 6rpx 8rpx;
}
}
}
</style>

156
uni-app/src/addon/shop/components/diy/shop-order-info/index.vue

@ -0,0 +1,156 @@
<template>
<view :style="warpCss">
<view class="diy-text relative">
<view class="px-[var(--pad-sidebar-m)] pt-[var(--pad-top-m)] pb-[40rpx] flex items-center justify-between">
<view @click="diyStore.toRedirect(diyComponent.link)">
<view class="max-w-[200rpx] truncate leading-[1] text-[30rpx]" :style="{ fontSize: diyComponent.fontSize * 2 + 'rpx', color: diyComponent.textColor, fontWeight: (diyComponent.fontWeight == 'normal' ? 500 : diyComponent.fontWeight) }">
{{ diyComponent.text }}
</view>
</view>
<view class="flex items-center">
<view @click="redirect({ url: '/addon/shop/pages/order/list'})" class="flex items-center">
<text class="max-w-[200rpx] truncate text-[24rpx]" :style="{ color: diyComponent.more.color }">{{ diyComponent.more.text }}</text>
<text class="nc-iconfont nc-icon-youV6xx text-[24rpx]" :style="{ color: diyComponent.more.color }"></text>
</view>
</view>
</view>
</view>
<view class="pb-[var(--pad-top-m)] px-[var(--pad-sidebar-m)] flex items-center justify-between text-center">
<view class="flex flex-col items-center w-[20%] flex-shrink-0" @click="toList(1)">
<view class="relative w-[44rpx] h-[44rpx]">
<image class="w-[44rpx] h-[44rpx]" :src="img('addon/shop/diy/member/order1.png')" />
<view v-if="orderInfo.wait_pay"
:class="['absolute left-[35rpx] top-[-10rpx] rounded-[28rpx] h-[28rpx] min-w-[28rpx] text-center leading-[30rpx] bg-[#FF4646] text-[#fff] text-[20rpx] font-500 box-border', orderInfo.wait_pay > 9 ? 'px-[10rpx]' : '']">
{{ orderInfo.wait_pay > 99 ? "99+" : orderInfo.wait_pay }}
</view>
</view>
<view class="mt-[20rpx] leading-[1]" :style="{
fontSize: diyComponent.item.fontSize * 2 + 'rpx',
color: diyComponent.item.color,
fontWeight: diyComponent.item.fontWeight
}">待付款</view>
</view>
<view class="flex flex-col items-center w-[20%] flex-shrink-0" @click="toList(2)">
<view class="relative w-[44rpx] h-[44rpx]">
<image class="w-[44rpx] h-[44rpx]" :src="img('addon/shop/diy/member/order2.png')" />
<view v-if="orderInfo.wait_shipping"
:class="['absolute left-[35rpx] top-[-10rpx] rounded-[28rpx] h-[28rpx] min-w-[28rpx] text-center leading-[30rpx] bg-[#FF4646] text-[#fff] text-[20rpx] font-500 box-border', orderInfo.wait_shipping > 9 ? 'px-[10rpx]' : '']">
{{ orderInfo.wait_shipping > 99 ? "99+" : orderInfo.wait_shipping }}
</view>
</view>
<view class="mt-[20rpx] leading-[1]" :style="{
fontSize: diyComponent.item.fontSize * 2 + 'rpx',
color: diyComponent.item.color,
fontWeight: diyComponent.item.fontWeight
}">待发货</view>
</view>
<view class="flex flex-col items-center w-[20%] flex-shrink-0" @click="toList(3)">
<view class="relative w-[44rpx] h-[44rpx]">
<image class="w-[44rpx] h-[44rpx]" :src="img('addon/shop/diy/member/order3.png')" />
<view v-if="orderInfo.wait_take"
:class="['absolute left-[35rpx] top-[-10rpx] rounded-[28rpx] h-[28rpx] min-w-[28rpx] text-center leading-[30rpx] bg-[#FF4646] text-[#fff] text-[20rpx] font-500 box-border', orderInfo.wait_take > 9 ? 'px-[10rpx]' : '']">
{{ orderInfo.wait_take > 99 ? "99+" : orderInfo.wait_take }}
</view>
</view>
<view class="mt-[20rpx] leading-[1]" :style="{
fontSize: diyComponent.item.fontSize * 2 + 'rpx',
color: diyComponent.item.color,
fontWeight: diyComponent.item.fontWeight
}">待收货</view>
</view>
<view class="flex flex-col items-center w-[20%] flex-shrink-0" @click="toList(5)">
<view class="relative w-[44rpx] h-[44rpx]">
<image class="w-[44rpx] h-[44rpx]" :src="img('addon/shop/diy/member/order4.png')" />
<view v-if="orderInfo.evaluate"
:class="['absolute left-[35rpx] top-[-10rpx] rounded-[28rpx] h-[28rpx] min-w-[28rpx] text-center leading-[30rpx] bg-[#FF4646] text-[#fff] text-[20rpx] font-500 box-border', orderInfo.evaluate > 9 ? 'px-[10rpx]' : '']">
{{ orderInfo.evaluate > 99 ? "99+" : orderInfo.evaluate }}
</view>
</view>
<view class="mt-[20rpx] leading-[1]" :style="{
fontSize: diyComponent.item.fontSize * 2 + 'rpx',
color: diyComponent.item.color,
fontWeight: diyComponent.item.fontWeight
}">待评价</view>
</view>
<view class="flex flex-col items-center w-[20%] flex-shrink-0" @click="redirect({ url: '/addon/shop/pages/refund/list'})">
<view class="relative w-[44rpx] h-[44rpx]">
<image class="w-[44rpx] h-[44rpx]" :src="img('addon/shop/diy/member/order5.png')" />
<view v-if="orderInfo.refund"
:class="['absolute left-[35rpx] top-[-10rpx] rounded-[28rpx] h-[28rpx] min-w-[28rpx] text-center leading-[30rpx] bg-[#FF4646] text-[#fff] text-[20rpx] font-500 box-border', orderInfo.refund > 9 ? 'px-[10rpx]' : '']">
{{ orderInfo.refund > 99 ? "99+" : orderInfo.refund }}
</view>
</view>
<view class="mt-[20rpx] leading-[1]" :style="{
fontSize: diyComponent.item.fontSize * 2 + 'rpx',
color: diyComponent.item.color,
fontWeight: diyComponent.item.fontWeight
}">售后/退款</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, watch,onMounted } from 'vue';
import useDiyStore from '@/app/stores/diy';
import { img,redirect } from '@/utils/common';
import {getShopOrderNum} from '@/addon/shop/api/order';
const props = defineProps(['component', 'index']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
onMounted(() => {
refresh();
});
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},{deep: true})
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
orderInfo.value = {
}
} else {
getShopOrderNumFn()
}
}
const orderInfo = ref({})
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const getShopOrderNumFn=()=>{
getShopOrderNum().then((res:any)=>{
orderInfo.value = res.data
})
}
const toList = (status:any) => {
redirect({ url: '/addon/shop/pages/order/list', param: { status } })
}
</script>
<style>
</style>

102
uni-app/src/addon/shop/components/diy/shop-search/index.vue

@ -0,0 +1,102 @@
<template>
<view :style="warpCss">
<view :style="maskLayer"></view>
<view class="diy-shop-search relative overflow-hidden flex items-center">
<image :src="img('addon/shop/diy/search_01.png')" class="w-[40rpx] h-[40rpx]" mode="widthFix" @click="toLink('/addon/shop/pages/goods/category')"></image>
<view class="flex-1 ml-[24rpx] rounded-[32rpx] flex items-center bg-[var(--temp-bg)] opacity-90 py-[10rpx] pl-[38rpx] pr-[32rpx] justify-between h-[60rpx] box-border" @click="toLink('/addon/shop/pages/goods/search')">
<text class="text-[var(--text-color-light9)] text-[26rpx]">{{ diyComponent.text }}</text>
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1 text-[24rpx]"></text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue';
import { img, redirect } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
const props = defineProps(['component', 'index']);
const diyStore = useDiyStore();
const diyComponent = computed(() => {
if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
const warpCss = computed(() => {
var style = '';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if(diyComponent.value.componentBgUrl) {
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`;
style += 'background-size: cover;background-repeat: no-repeat;';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
//
const maskLayer = computed(()=>{
var style = '';
if(diyComponent.value.componentBgUrl) {
style += 'position:absolute;top:0;width:100%;';
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`;
style += `height:${height.value}px;`;
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
}
return style;
});
onMounted(() => {
refresh();
//
if (diyStore.mode == 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
if (newValue && newValue.componentName == 'ShopSearch') {
refresh();
}
}
)
}
});
const instance = getCurrentInstance();
const height = ref(0)
const refresh = ()=> {
nextTick(() => {
const query = uni.createSelectorQuery().in(instance);
query.select('.diy-shop-search').boundingClientRect((data: any) => {
height.value = data.height;
}).exec();
})
}
const toLink = (url: any)=>{
if (diyStore.mode == 'decorate') return false;
redirect({ url })
}
</script>
<style lang="scss" scoped>
</style>

255
uni-app/src/addon/shop/components/diy/single-recommend/index.vue

@ -0,0 +1,255 @@
<template>
<view :style="warpCss" class="overflow-hidden" v-if="goodsList && goodsList[0]">
<view class="flex justify-between items-center mb-[20rpx]" v-if="diyComponent.textImg || diyComponent.subTitle.text">
<view class="h-[34rpx] flex items-center" v-if="diyComponent.textImg" @click="diyStore.toRedirect(diyComponent.textLink)">
<image class="h-[100%] w-[auto]" :src="img(diyComponent.textImg)" mode="heightFix" />
</view>
<view class="flex items-center ml-[auto]" v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" :style="{'color': diyComponent.subTitle.textColor}">
<text class="text-[24rpx]">{{diyComponent.subTitle.text}}</text>
<text class="text-[22rpx] iconfont iconxiangyoujiantou"></text>
</view>
</view>
<view class="flex justify-between">
<!-- 轮播图 -->
<view class="relative w-[340rpx] overflow-hidden" :style="carouselCss">
<view v-if="diyComponent.list.length == 1" class="leading-0 overflow-hidden">
<view @click="diyStore.toRedirect(diyComponent.list[0].link)">
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" mode="heightFix" class="h-[504rpx] !w-full" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="heightFix" class="h-[504rpx] !w-full" :show-menu-by-longpress="true"/>
</view>
</view>
<block v-else>
<swiper class="swiper ns-indicator-dots-three h-[504rpx]" autoplay="true" circular="true" :indicator-dots="isShowDots" @change="swiperChange"
:indicator-color="diyComponent.indicatorColor" :indicator-active-color="diyComponent.indicatorActiveColor">
<swiper-item class="swiper-item" v-for="(item) in diyComponent.list" :key="item.id">
<view @click="diyStore.toRedirect(item.link)">
<view class="item h-[504rpx]">
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/>
</view>
</view>
</swiper-item>
</swiper>
<!-- #ifdef MP-WEIXIN -->
<view v-if="diyComponent.list.length > 1" class="swiper-dot-box straightLineStyle2">
<view v-for="(numItem, numIndex) in diyComponent.list" :key="numIndex" :class="['swiper-dot', { active: numIndex == swiperIndex }]" :style="[numIndex == swiperIndex ? { backgroundColor: diyComponent.indicatorActiveColor } : { backgroundColor: diyComponent.indicatorColor }]"></view>
</view>
<!-- #endif -->
</block>
</view>
<view class="w-[340rpx] h-[504rpx] flex flex-col bg-[#fff] box-border overflow-hidden" :style="goodsTempCss" @click="toLink(goodsList[0])">
<view :style="goodsImgCss" class="w-[346rpx] h-[350rpx] overflow-hidden">
<u--image width="346rpx" height="350rpx" :src="img(goodsList[0].goods_cover_thumb_mid || '')" model="aspectFill">
<template #error>
<image class="w-[346rpx] h-[350rpx]" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="px-[16rpx] flex-1 pt-[16rpx] pb-[20rpx] flex flex-col justify-between">
<view class="text-[#303133] leading-[40rpx] text-[28rpx] truncate" :style="{ color : diyComponent.goodsNameStyle.color, fontWeight : diyComponent.goodsNameStyle.fontWeight }">
{{goodsList[0].goods_name}}
</view>
<view class="flex justify-between flex-wrap items-baseline mt-[28rpx]" >
<view class="flex items-center">
<view class="text-[var(--price-text-color)] price-font truncate max-w-[200rpx]" :style="{ color : diyComponent.priceStyle.mainColor }">
<text class="text-[24rpx] font-400"></text>
<text class="text-[40rpx] font-500">{{ parseFloat(diyGoods.goodsPrice(goodsList[0])).toFixed(2).split('.')[0] }}</text>
<text class="text-[24rpx] font-500">.{{ parseFloat(diyGoods.goodsPrice(goodsList[0])).toFixed(2).split('.')[1] }}</text>
</view>
<image v-if="diyGoods.priceType(goodsList[0]) == 'member_price'" class="max-w-[50rpx] h-[28rpx] ml-[6rpx]" :src="img('addon/shop/VIP.png')" mode="heightFix" />
</view>
<view class="w-[44rpx] h-[44rpx] bg-[red] flex items-center justify-center rounded-[50%]" :style="{ backgroundColor : diyComponent.saleStyle.color }">
<text class="iconfont iconjia font-500 text-[32rpx] text-[#fff]"></text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
//
import { ref,reactive,computed, watch, onMounted } from 'vue';
import { redirect, img } from '@/utils/common';
import useDiyStore from '@/app/stores/diy';
import { getGoodsComponents } from '@/addon/shop/api/goods';
import {useGoods} from '@/addon/shop/hooks/useGoods'
const diyGoods = useGoods();
const props = defineProps(['component', 'index','value']);
const diyStore = useDiyStore();
const goodsList = ref<Array<any>>([]);
const diyComponent = computed(() => {
if(props.value) {
return props.value;
}else if (diyStore.mode == 'decorate') {
return diyStore.value[props.index];
} else {
return props.component;
}
})
//
let isShowDots = ref(true)
// #ifdef H5
isShowDots.value = true;
// #endif
// #ifdef MP-WEIXIN
isShowDots.value = false;
// #endif
const warpCss = computed(() => {
var style = '';
style += 'position:relative;';
if(diyComponent.value.componentStartBgColor) {
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`;
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';';
}
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
return style;
})
const goodsTempCss = computed(() => {
var style = '';
if (diyComponent.value.elementBgColor) style += 'background-color:' + diyComponent.value.elementBgColor + ';';
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
if(diyComponent.value.margin && diyComponent.value.margin.both) style += 'width: calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 20rpx) / 2);'
else style += 'width: calc((100vw - 20rpx) / 2 );'
return style;
})
const goodsImgCss = computed(() => {
var style = '';
if (diyComponent.value.topElementRounded) style += 'border-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
return style;
})
const carouselCss = computed(() => {
var style = '';
if (diyComponent.value.topCarouselRounded) style += 'border-top-left-radius:' + diyComponent.value.topCarouselRounded * 2 + 'rpx;';
if (diyComponent.value.topCarouselRounded) style += 'border-top-right-radius:' + diyComponent.value.topCarouselRounded * 2 + 'rpx;';
if (diyComponent.value.bottomCarouselRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomCarouselRounded * 2 + 'rpx;';
if (diyComponent.value.bottomCarouselRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomCarouselRounded * 2 + 'rpx;';
if(diyComponent.value.margin && diyComponent.value.margin.both) style += 'width: calc((100vw - ' + (diyComponent.value.margin.both*4) + 'rpx - 20rpx) / 2);'
else style += 'width: calc((100vw - 20rpx) / 2 );'
return style;
})
const getGoodsListFn = () => {
let data = {
num: 1,
goods_ids: diyComponent.value.source == 'custom' ? diyComponent.value.goods_ids : ''
}
getGoodsComponents(data).then((res) => {
goodsList.value = res.data;
});
}
onMounted(() => {
refresh();
//
if (diyStore.mode != 'decorate') {
watch(
() => diyComponent.value,
(newValue, oldValue) => {
refresh();
},
{deep: true}
)
}
});
const refresh = () => {
//
if (diyStore.mode == 'decorate') {
let obj = {
goods_cover_thumb_mid: "",
goods_name: "商品名称",
sale_num: "100",
unit: "件",
goodsSku:{price:100}
};
goodsList.value.push(obj);
}else{
getGoodsListFn();
}
}
const toLink = (data: any) => {
redirect({ url: '/addon/shop/pages/goods/detail', param: { goods_id: data.goods_id } })
}
const swiperIndex = ref(0);
const swiperChange = e => {
swiperIndex.value = e.detail.current;
};
</script>
<style lang="scss" scoped>
.swiper.ns-indicator-dots-three :deep(.uni-swiper-dots-horizontal) {
bottom: 12rpx;
}
.swiper.ns-indicator-dots-three :deep(.uni-swiper-dot) {
width: 8rpx;
height: 8rpx;
border-radius: 8rpx;
margin-right: 14rpx;
}
.swiper.ns-indicator-dots-three :deep(.uni-swiper-dot):last-of-type {
margin-right: 0;
}
.swiper.ns-indicator-dots-three :deep(.uni-swiper-dot-active) {
width: 30rpx;
}
.swiper-dot-box {
position: absolute;
bottom: 4rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 80rpx 8rpx;
box-sizing: border-box;
.swiper-dot {
background-color: #b2b2b2;
width: 10rpx;
border-radius: 50%;
height: 10rpx;
margin: 8rpx;
}
&.straightLineStyle2{
.swiper-dot {
width: 8rpx;
height: 8rpx;
border-radius: 8rpx;
margin: 0;
margin-right: 14rpx;
&.last-of-type {
margin-right: 0;
}
&.active {
width: 30rpx;
}
}
}
}
</style>

141
uni-app/src/addon/shop/components/ns-goods-manjian/ns-goods-manjian.vue

@ -0,0 +1,141 @@
<template>
<!-- 满减 -->
<view @touchmove.prevent.stop>
<u-popup class="manjian-popup" :show="manjianShow" @close="manjianShow = false" zIndex="999999">
<view class="min-h-[480rpx] popup-common" @touchmove.prevent.stop>
<view class="title !pb-[30rpx]">满减送</view>
<scroll-view class="h-[520rpx]" scroll-y="true">
<view class="px-[var(--popup-sidebar-m)] pt-[30rpx]">
<view v-for="(item,index) in data.content" :key="index" class="mb-[40rpx]">
<view class="flex items-center">
<text class="nc-iconfont nc-icon-qianbaoyueV6xx !text-[28rpx] mr-[10rpx]"></text>
<text class="text-[26rpx] font-500">{{item.limit}}</text>
</view>
<view class="mt-[20rpx]">
<view v-if="item.goods && item.goods.length" class="flex mt-[20rpx]">
<view class="w-[100rpx] flex justify-end">
<view class="bg-[var(--primary-color-light)] text-[var(--primary-color)] rounded-[6rpx] text-[22rpx] flex items-center justify-center px-[12rpx] h-[38rpx] mr-[6rpx]">赠品</view>
</view>
<view class="flex-1 ml-[8rpx]">
<view class="flex p-[20rpx] bg-[#f8f8f8] rounded-[var(--goods-rounded-big)] overflow-hidden" :class="{'mb-[20rpx]': goodsIndex != (item.goods.length-1)}" v-for="(goodsItem,goodsIndex) in item.goods" :key="goodsIndex" @click="goodsEvent(goodsItem.goods_id)">
<u--image radius="var(--goods-rounded-mid)" width="120rpx" height="120rpx" :src="img(goodsItem.sku_image)" model="aspectFill">
<template #error>
<image class="w-[120rpx] h-[120rpx] rounded-[var(--goods-rounded-big)] overflow-hidden" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
<view class="flex flex-1 w-0 flex-col justify-between ml-[20rpx] pt-[6rpx] pb-[10rpx]">
<view class="truncate text-[#303133] text-[24rpx] leading-[32rpx]">
{{goodsItem.goods_name}}
</view>
<view class="flex items-baseline">
<view v-if="goodsItem.sku_name" class="truncate text-[22rpx] mt-[4rpx] text-[#999]">
{{ goodsItem.sku_name }}
</view>
<view class="font-400 ml-[auto] text-[24rpx] text-[#303133]">
<text>x</text>
<text>{{goodsItem.num}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<block v-if="item.give && item.give.length">
<view class="flex items-center mt-[24rpx]" v-for="(giveItem,giveIndex) in item.give" :key="giveIndex">
<view class="w-[100rpx] flex justify-end">
<view class="bg-[var(--primary-color-light)] text-[var(--primary-color)] rounded-[6rpx] text-[22rpx] flex items-center justify-center px-[12rpx] h-[38rpx] mr-[6rpx]">{{giveItem.label}}</view>
</view>
<text class="text-[24rpx]">{{giveItem.content}}</text>
</view>
</block>
<view class="flex items-baseline mt-[24rpx]" v-if="item.coupon && item.coupon.length">
<view class="w-[100rpx] flex justify-end">
<view class="bg-[var(--primary-color-light)] text-[var(--primary-color)] rounded-[6rpx] text-[22rpx] flex items-center justify-center px-[12rpx] h-[38rpx] mr-[6rpx]">优惠券</view>
</view>
<view class="flex flex-wrap flex-1">
<text class="flex items-center text-[24rpx] leading-[1.3]" :class="{'mb-[16rpx]': couponIndex != (item.coupon.length-1)}" v-for="(couponItem,couponIndex) in item.coupon" :key="couponIndex">
{{couponItem.num}}{{couponItem.coupon_name}}优惠券
</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="btn-wrap">
<button class="primary-btn-bg btn" @click="manjianShow = false">确定</button>
</view>
</view>
</u-popup>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { img, deepClone, redirect } from '@/utils/common'
import { cloneDeep } from 'lodash-es'
import { t } from '@/locale'
const manjianShow = ref(false);
const data = ref({});
const open = (parameter:any = {})=>{
data.value = cloneDeep(parameter);
data.value.content = [];
data.value.rule_json.forEach((item,index)=>{
if(item.is_show || item.is_show == undefined){
let obj = {};
obj.limit = `门槛满${data.value.condition_type == 'over_n_yuan' ? parseFloat(item.limit).toFixed(2) : item.limit }${data.value.condition_type == 'over_n_yuan' ? '元' : '件'}`;
if(item.is_give_goods){
obj.goods = deepClone(item.goods);
}
obj.give = [];
if(item.is_discount && item.discount_money){
obj.give.push({
label: '满减',
content: `订单金额${item.discount_type == 1 ? '减' : '打'}${item.discount_type == 1 ? parseFloat(item.discount_money).toFixed(2) : item.discount_money}${item.discount_type == 1 ? '元' : '折'}`
})
}
if(item.is_free_shipping){
obj.give.push({
label: '包邮',
content: '商品包邮'
});
}
if(item.is_give_point && item.point){
obj.give.push({
label: '积分',
content: `${item.point}积分`
});
}
if(item.is_give_balance && item.balance){
obj.give.push({
label: '余额',
content: `${parseFloat(item.balance).toFixed(2)}余额`
});
}
if(item.is_give_coupon){obj.coupon = item.coupon;}
data.value.content.push(obj);
}
});
manjianShow.value = true;
}
const goodsEvent = (id : number) => {
redirect({
url: '/addon/shop/pages/goods/detail',
param: {
goods_id: id
}
})
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
::v-deep .manjian-popup .u-slide-up-enter-to{
z-index: 999999 !important;
}
</style>

69
uni-app/src/addon/shop/components/ns-goods-recommend/ns-goods-recommend.vue

@ -0,0 +1,69 @@
<template>
<view class="goods-recommend">
<view class="mt-[60rpx] flex flex-col items-center sidebar-margin pb-[50rpx]">
<view class="flex items-center mb-[30rpx]" v-if="goodsList && Object.keys(goodsList).length">
<image class="w-[38rpx] h-[22rpx]" :src="img('addon/shop_fenxiao/level/title_left.png')" mode="aspectFill"></image>
<text class="text-[30rpx] mx-[18rpx] font-500 text-[#EF000C]">猜你喜欢</text>
<image class="w-[38rpx] h-[22rpx]" :src="img('addon/shop_fenxiao/level/title_right.png')" mode="aspectFill"></image>
</view>
<diy-goods-list @loadingFn="getGoodsListFn" :component="goodsData"/>
</view>
</view>
</template>
<script setup lang="ts">
import { img } from '@/utils/common';
import { ref } from 'vue'
import diyGoodsList from '@/addon/shop/components/diy/goods-list/index.vue';
//
const goodsList = ref()
const getGoodsListFn = (data: any)=>{
goodsList.value = data || {}
}
//
const goodsData = ref({
style: 'style-2',
num: 10,
source: 'all',
topElementRounded: 12,
bottomElementRounded: 12,
margin: {
both: 10,
bottom: 0,
top: 0
},
priceStyle: {
mainColor: "#FF4142",
control: true
},
goodsNameStyle:{
color: "#303133",
control: true,
fontWeight: "normal"
},
saleStyle: {
color: "#999",
control: true
},
labelStyle: {
isShow: true,
control: true
},
btnStyle: {
fontWeight:false,
padding: 0,
aroundRadius:25,
textColor: "#fff",
startBgColor: '#FF4142',
endBgColor: '#FF4142',
style: 'nc-icon-gouwuche1',
control: true
}
});
</script>
<style lang="scss" scoped>
</style>

459
uni-app/src/addon/shop/components/ns-goods-sku/ns-goods-sku.vue

@ -0,0 +1,459 @@
<template>
<view @touchmove.prevent.stop>
<u-overlay :show="goodsSkuPop" @click="closeFn" zIndex="490">
<u-popup class="popup-type" :show="goodsSkuPop" @close="closeFn" mode="bottom" :overlay="false" zIndex="500">
<view class="py-[32rpx] relative" v-if="goodsDetail.detail" @touchmove.prevent.stop>
<view class="flex px-[32rpx]" :class="{'mb-[58rpx]':!(goodsDetail.is_newcomer && goodsDetail.newcomer_price != goodsDetail.price && (Object.keys(cartSkuList).length?parseInt(cartSkuList.num)+buyNum:buyNum)>1)}">
<view class="rounded-[var(--goods-rounded-big)] overflow-hidden w-[180rpx] h-[180rpx]">
<u--image width="180rpx" height="180rpx" :src="img(goodsDetail.detail.sku_image)" @click="imgListPreview(goodsDetail.detail.sku_image)" model="aspectFill">
<template #error>
<image class="w-[180rpx] h-[180rpx]" :src="img('static/resource/images/diy/shop_default.jpg')" mode="aspectFill"></image>
</template>
</u--image>
</view>
<view class="flex flex-1 flex-col justify-between ml-[24rpx] py-[10rpx]">
<view class="w-[100%]">
<view class="text-[var(--price-text-color)] flex items-baseline">
<text class="text-[32rpx] font-bold price-font"></text>
<text class="text-[48rpx] price-font">{{ parseFloat(goodsPrice).toFixed(2).split('.')[0] }}</text>
<text class="text-[32rpx] mr-[6rpx] price-font">.{{ parseFloat(goodsPrice).toFixed(2).split('.')[1] }}</text>
<image class="h-[24rpx] ml-[6rpx] max-w-[60rpx]" v-if="priceType() == 'newcomer_price'" :src="img('addon/shop/newcomer.png')" mode="heightFix" />
<image class="h-[24rpx] ml-[6rpx] max-w-[44rpx]" v-if="priceType() == 'member_price'" :src="img('addon/shop/VIP.png')" mode="heightFix" />
<image class="h-[24rpx] ml-[6rpx] max-w-[72rpx]" v-if="priceType() == 'discount_price'" :src="img('addon/shop/discount.png')" mode="heightFix" />
</view>
<view class="text-[26rpx] leading-[32rpx] text-[var(--text-color-light6)] mt-[12rpx]">库存{{goodsDetail.detail.stock}}{{ goodsDetail.goods.unit }}</view>
</view>
<view class="text-[26rpx] leading-[30rpx] text-[var(--text-color-light6)] w-[100%] max-h-[60rpx] multi-hidden" v-if="goodsDetail.goodsSpec && goodsDetail.goodsSpec.length">已选规格{{goodsDetail.detail.sku_spec_format}}</view>
<!-- <view v-if="goodsDetail.goodsSpec && goodsDetail.goodsSpec.length">
<text>已选规格{{goodsDetail.detail.sku_spec_format}}</text>
</view> -->
</view>
</view>
<view class="flex items-center px-[32rpx] pt-[8rpx] pb-[16rpx] h-[58rpx] box-border" v-if="goodsDetail.is_newcomer && goodsDetail.newcomer_price != goodsDetail.price && (Object.keys(cartSkuList).length?parseInt(cartSkuList.num)+buyNum:buyNum)>1">
<image class="h-[24rpx] w-[56rpx]" :src="img('addon/shop/newcomer.png')" mode="aspectFit" />
<view class="text-[24rpx] text-[#FFB000] leading-[34rpx] ml-[8rpx]">第1{{goodsDetail.goods.unit}}{{parseFloat(goodsDetail.newcomer_price).toFixed(2)}}/{{goodsDetail.goods.unit}}{{(parseInt(cartSkuList.num||0)+buyNum)>2?'2~'+(parseInt(cartSkuList.num||0)+buyNum):'2'}}{{goodsDetail.goods.unit}}{{parseFloat(parseFloat(goodsPrice)).toFixed(2)}}/{{goodsDetail.goods.unit}}</view>
</view>
<scroll-view class="h-[500rpx] px-[32rpx] box-border mb-[60rpx]" scroll-y="true">
<view :class="{'mt-[20rpx]': 0 != index }" v-for="(item,index) in goodsDetail.goodsSpec" :key="index">
<view class="text-[28rpx] leading-[36rpx] mb-[24rpx]">{{item.spec_name}}</view>
<view class="flex flex-wrap">
<view class="box-border bg-[var(--temp-bg)] text-[24rpx] px-[44rpx] text-center h-[56rpx] flex-center mr-[20rpx] mb-[20rpx] border-1 border-solid rounded-[50rpx] border-[var(--temp-bg)]"
:class="{'!border-[var(--primary-color)] text-[var(--primary-color)] !bg-[var(--primary-color-light)]': subItem.selected}"
v-for="(subItem,subIndex) in item.values" :key="subIndex" @click="change(subItem, index)">
{{subItem.name}}
</view>
</view>
</view>
<view class="flex justify-between items-center my-[20rpx]">
<view class="text-[28rpx]">购买数量</view>
<text v-if="maxBuyShow > 0 && minBuyShow > 1" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">
({{ minBuyShow }}{{ goodsDetail.goods.unit }}起售限购{{ maxBuyShow }}{{ goodsDetail.goods.unit }})
</text>
<text v-else-if="maxBuyShow > 0" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">(限购{{ maxBuyShow }}{{ goodsDetail.goods.unit }})</text>
<text v-else-if="minBuyShow > 1" class="ml-[20rpx] mr-[auto] text-[24rpx] text-[var(--primary-color)]">({{ minBuyShow }}{{ goodsDetail.goods.unit }}起售)</text>
<u-number-box :min="minBuy" :max="maxBuy" integer :step="1" input-width="68rpx" v-model="buyNum" input-height="52rpx">
<template #minus>
<view class="relative w-[30rpx] h-[30rpx]" @click="reduceNumChange">
<text class="text-[30rpx] nc-iconfont nc-icon-jianV6xx font-500 absolute flex items-center justify-center -left-[8rpx] -bottom-[8rpx] -right-[8rpx] -top-[8rpx]" :class="{ '!text-[var(--text-color-light9)]': buyNum <= minBuy }"></text>
</view>
</template>
<template #input>
<input class="text-[#303133] text-[28rpx] mx-[10rpx] w-[80rpx] h-[44rpx] bg-[var(--temp-bg)] leading-[44rpx] text-center rounded-[6rpx]" type="number" @input="goodsSkuInputFn" @blur="goodsSkuBlurFn" v-model="buyNum" />
</template>
<template #plus>
<view class="relative w-[30rpx] h-[30rpx]" @click="addNumChange">
<text class="text-[30rpx] nc-iconfont nc-icon-jiahaoV6xx font-500 absolute flex items-center justify-center -left-[8rpx] -bottom-[8rpx] -right-[8rpx] -top-[8rpx]" :class="{ '!text-[var(--text-color-light9)]': buyNum >= maxBuy }"></text>
</view>
</template>
</u-number-box>
</view>
<view class="mt-[40rpx]">
<diy-form ref="diyFormRef" :form_id="goodsDetail.goods.form_id" :storage_name="'diyFormStorageByGoodsDetail_' + goodsDetail.sku_id" />
</view>
</scroll-view>
<view class="px-[20rpx]">
<!-- #ifdef H5 -->
<button v-if="goodsDetail.detail.stock > 0" hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" @click="confirm">确定</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<template v-if="goodsDetail.detail.stock > 0">
<button v-if="isBindMobile && userInfo && !userInfo.mobile" hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" open-type="getPhoneNumber" @getphonenumber="memberStore.bindMobile">确定</button>
<button v-else hover-class="none" class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 rounded-[50rpx] primary-btn-bg" type="primary" @click="confirm">确定</button>
</template>
<!-- #endif -->
<button hover-class="none" v-else class="!h-[80rpx] leading-[80rpx] text-[26rpx] font-500 text-[#fff] bg-[#ccc] rounded-[50rpx]">已售罄</button>
</view>
</view>
</u-popup>
</u-overlay>
<!-- 强制绑定手机号 -->
<bind-mobile ref="bindMobileRef" />
</view>
</template>
<script setup lang="ts">
import { ref, computed, toRaw } from 'vue';
import { img, redirect, getToken } from '@/utils/common'
import useCartStore from '@/addon/shop/stores/cart'
import { useLogin } from '@/hooks/useLogin'
import useMemberStore from '@/stores/member'
import bindMobile from '@/components/bind-mobile/bind-mobile.vue';
import { cloneDeep } from 'lodash-es'
import { t } from '@/locale'
import diyForm from '@/addon/components/diy-form/index.vue'
const props = defineProps(['goodsDetail']);
const goodsSkuPop = ref(false);
const callback:any = ref(null);
const currSpec = ref({
skuId: "",
name: []
})
const openType = ref("");
const buyNum = ref(1)
const maxBuy = ref(0); //
const minBuy = ref(0); //
const maxBuyShow = ref(0); //
const minBuyShow = ref(0); //
//
const goodsPrice = computed(() => {
let price = "0.00";
if (Object.keys(goodsDetail.value).length && goodsDetail.value.type == 'newcomer_discount' && goodsDetail.value.is_newcomer && goodsDetail.value.newcomer_price != goodsDetail.value.price && (Object.keys(cartSkuList.value).length ? parseInt(cartSkuList.value.num) + buyNum.value : buyNum.value) < 2) {
//
price = goodsDetail.value.newcomer_price;
} else if (Object.keys(goodsDetail.value).length && goodsDetail.value.type == 'discount' && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.is_discount && goodsDetail.value.sale_price != goodsDetail.value.price) {
price = goodsDetail.value.sale_price //
} else if (Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.member_discount && getToken() && goodsDetail.value.member_price != goodsDetail.value.price) {
price = goodsDetail.value.member_price //
} else {
price = goodsDetail.value.price
}
return price;
})
//
const priceType = () => {
let type = "";
if (goodsDetail.value.type == 'newcomer_discount' && Object.keys(goodsDetail.value).length && goodsDetail.value.is_newcomer && goodsDetail.value.newcomer_price != goodsDetail.value.price && getToken()) {
type = 'newcomer_price'//
} else if (goodsDetail.value.type == 'discount' && Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.is_discount && goodsDetail.value.sale_price != goodsDetail.value.price) {
type = 'discount_price'//
} else if (Object.keys(goodsDetail.value).length && Object.keys(goodsDetail.value.goods).length && goodsDetail.value.goods.member_discount && getToken() && goodsDetail.value.member_price != goodsDetail.value.price) {
type = 'member_price' //
}
return type;
}
//
const memberStore = useMemberStore()
const userInfo = computed(() => memberStore.info)
//
const cartStore = useCartStore();
cartStore.getList();
const cartSkuList = computed(()=>{
if(goodsDetail.value && cartStore.cartList['goods_' + goodsDetail.value.goods_id] && cartStore.cartList['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id]) {
return cartStore.cartList['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id];
}else{
return {}
}
})
const cartList = computed(() => cartStore.cartList)
const open = (type="",fn = "")=>{
openType.value = type;
goodsSkuPop.value = true;
callback.value = fn;
}
const goodsSkuInputFn = ()=>{
setTimeout(() => {
if(!buyNum.value || buyNum.value <= minBuy.value ){
buyNum.value = minBuy.value || 1;
}
if(buyNum.value >= maxBuy.value){
buyNum.value = maxBuy.value;
}
//
if(minBuy.value > goodsDetail.value.detail.stock){
buyNum.value = 0;
}
},0)
}
const goodsSkuBlurFn = ()=>{
setTimeout(() => {
if(!buyNum.value || buyNum.value <= minBuy.value ){
buyNum.value = minBuy.value || 1;
}
if(buyNum.value >= maxBuy.value){
buyNum.value = maxBuy.value;
}
//
if(minBuy.value > goodsDetail.value.detail.stock){
buyNum.value = 0;
uni.showToast({
title: '商品库存小于起购数量',
icon: 'none'
});
}
},0)
}
const closeFn = ()=>{
goodsSkuPop.value = false
}
const goodsDetail = computed(() => {
let data = cloneDeep(props.goodsDetail);
//
if(Object.keys(data).length){
if(!Object.keys(currSpec.value.name).length) currSpec.value.name = data.sku_spec_format.split(",");
data.goodsSpec.forEach((item: any,index: any)=>{
let specName = item.spec_values.split(",");
item.values = [];
specName.forEach((specItem: any, specIndex: any)=>{
item.values[specIndex] = {};
item.values[specIndex].name = specItem;
item.values[specIndex].selected = false;
item.values[specIndex].disabled = false;
//
currSpec.value.name.forEach((currSpecItem, currSpecIndex)=>{
if(currSpecIndex == index && currSpecItem == specItem){
item.values[specIndex].selected = true;
}
})
})
})
getSkuId();
//
if(data.skuList && Object.keys(data.skuList).length){
data.skuList.forEach((idItem: any, idIndex: any)=>{
if(idItem.sku_id == currSpec.value.skuId){
data.detail = idItem;
}
})
}
}
// -
if(data.goods.is_limit){
if(data.goods.max_buy){
let max_buy = 0;
if(data.goods.limit_type == 1){ //
max_buy = data.goods.max_buy;
}else{ //
let buyVal = data.goods.max_buy - (data.goods.has_buy||0);
max_buy = buyVal > 0 ? buyVal : 0;
}
if(max_buy > data.detail.stock){
maxBuy.value = data.detail.stock
}else if(max_buy <= data.detail.stock){
maxBuy.value = max_buy;
}
//
if(maxBuy.value == 0){
buyNum.value = 0;
}
}
//
maxBuyShow.value = data.goods.max_buy; //
}else{
maxBuy.value = data.detail.stock;
}
//
minBuy.value = data.goods.min_buy > 0 ? data.goods.min_buy : 1;
//
if(minBuy.value > data.detail.stock){
buyNum.value = 0;
}else{
buyNum.value = minBuy.value;
}
//
minBuyShow.value = data.goods.min_buy;
return data;
})
const change = (data: any, index: any)=>{
currSpec.value.name[index] = data.name;
getSkuId(); //
}
const emits = defineEmits(['change'])
const getSkuId = ()=>{
props.goodsDetail.skuList.forEach((skuItem: any, skuIndex: any)=>{
if(skuItem.sku_spec_format == currSpec.value.name.toString()){
currSpec.value.skuId = skuItem.sku_id
emits('change',skuItem.sku_id)
}
})
}
const addNumChange = () => {
if(minBuy.value && minBuy.value > goodsDetail.value.detail.stock){
uni.showToast({ title: '商品库存小于起购数量', icon: 'none' })
return;
}
if(goodsDetail.value.goods.is_limit){
let tips = `该商品单次限购${goodsDetail.value.goods.max_buy}`;
if(goodsDetail.value.goods.limit_type != 1){ //
tips = `该商品每人限购${goodsDetail.value.goods.max_buy}`;
if(goodsDetail.value.goods.max_buy - maxBuy.value){
tips += `,已购${goodsDetail.value.goods.max_buy - maxBuy.value}`;
}
}
if(buyNum.value >= maxBuy.value){
uni.showToast({ title: tips, icon: 'none' })
}
}
}
const reduceNumChange = () => {
if(minBuy.value > 1){
let tips = `该商品起购${minBuy.value}`;
if(buyNum.value <= minBuy.value){
uni.showToast({ title: tips, icon: 'none' })
}
}
}
//
const bindMobileRef: any = ref(null)
const isBindMobile = ref(uni.getStorageSync('isbindmobile'))
const diyFormRef: any = ref(null)
//
const confirm = ()=> {
if(!diyFormRef.value.verify()) return;
if(buyNum.value < 1) return;
//
if (!userInfo.value) {
useLogin().setLoginBack({
url: '/addon/shop/pages/goods/detail',
param: {
sku_id: goodsDetail.value.sku_id,
type: goodsDetail.value.type
}
})
return false
}
// #ifdef H5
//
if (uni.getStorageSync('isbindmobile')) {
bindMobileRef.value.open()
return false
}
// #endif
//
if (openType.value == 'join_cart') {
let num = 0;
let limitNum = 0;
let cartId = "";
if (cartList.value['goods_' + goodsDetail.value.goods_id] && cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id]) {
num = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id].num);
cartId = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id]['sku_' + goodsDetail.value.sku_id].id)
}
if (cartList.value['goods_' + goodsDetail.value.goods_id] && cartList.value['goods_' + goodsDetail.value.goods_id]) {
limitNum = toRaw(cartList.value['goods_' + goodsDetail.value.goods_id].totalNum);
}
num += Number(buyNum.value);
limitNum += Number(buyNum.value);
/************** 限购-start **************/
if(goodsDetail.value.goods.is_limit){
let tips = `该商品单次限购${goodsDetail.value.goods.max_buy}`;
if(goodsDetail.value.goods.limit_type != 1){ //
tips = `该商品每人限购${goodsDetail.value.goods.max_buy}`;
if(goodsDetail.value.goods.max_buy - maxBuy.value){
tips += `,已购${goodsDetail.value.goods.max_buy - maxBuy.value}`;
}
}
if(limitNum > maxBuy.value){
uni.showToast({ title: tips, icon: 'none' })
return false;
}
}
/************** 限购-end **************/
cartStore.increase({
id: cartId || '',
goods_id: goodsDetail.value.goods_id,
sku_id: goodsDetail.value.sku_id,
stock: goodsDetail.value.stock,
sale_price: goodsDetail.value.sale_price,
num: num
}, 0, () => {
uni.showToast({
title: '加入购物车成功',
icon: 'none'
});
});
} else if (openType.value == 'buy_now') {
//
var data = {
sku_id: goodsDetail.value.sku_id,
num: buyNum.value
};
uni.setStorage({
key: 'orderCreateData',
data: {
sku_data: [
data
],
extend_data: {
relate_id: '',
activity_type: goodsDetail.value.type //
}
},
success: () => {
redirect({ url: '/addon/shop/pages/order/payment' })
}
});
}
closeFn();
}
//
const imgListPreview = (item: any) => {
if (item === '') return false
var urlList = []
urlList.push(img(item)) //push :src="item.img_url"
uni.previewImage({
indicator: "number",
loop: true,
urls: urlList
})
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
::v-deep .u-number-box .u-number-box__slot{
display: flex;
align-items: center;
}
</style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save