实现后台及前台通过API访问UV埋点,所有代码全部保存

This commit is contained in:
2025-11-08 18:15:26 +08:00
parent 6bad32d9b1
commit e440631275
43 changed files with 5960 additions and 1105 deletions

60
docs/common/const.md Normal file
View File

@@ -0,0 +1,60 @@
# 常量的定义
src\app\event\init\InitConfig.php
```
/**
* 初始化常量
*/
private function initConst()
{
//加载版本信息
define('SHOP_MODULE', 'shop');
defined('SYS_VERSION_NO') or define('SYS_VERSION_NO', Config::get('info.version')); //版本号
defined('SYS_VERSION_NAME') or define('SYS_VERSION_NAME', Config::get('info.title')); //版本名称
defined('SYS_VERSION') or define('SYS_VERSION', Config::get('info.name')); //版本类型
defined('SYS_RELEASE') or define('SYS_RELEASE', Config::get('info.version_no')); //版本号
//是否展示帮助快捷链接
define('HELP_SHOW', 1);
//加载基础化配置信息
define('__ROOT__', str_replace([ '/index.php', '/install.php' ], '', request()->root(true)));
define('__PUBLIC__', __ROOT__ . '/public');
define('__UPLOAD__', 'upload');
//插件目录名称
define('ADDON_DIR_NAME', 'addon');
//插件目录路径
define('ADDON_PATH', 'addon/');
//分页每页数量
define('PAGE_LIST_ROWS', 10);
define('MEMBER_LEVEL', 10);
//伪静态模式是否开启
define('REWRITE_MODULE', true);
// public目录绝对路径
define('PUBLIC_PATH', root_path() . '/public/');
// 项目绝对路径
define('ROOT_PATH', root_path());
//兼容模式访问
if (!REWRITE_MODULE) {
define('ROOT_URL', request()->root(true) . '/?s=');
} else {
define('ROOT_URL', request()->root(true));
}
//检测网址访问
$url = request()->url(true);
$url = strtolower($url);
if (strstr($url, 'call_user_func_array') || strstr($url, 'invokefunction') || strstr($url, 'think\view')) {
die("非法请求");
}
// 应用模块
$GLOBALS[ 'system_array' ] = [ 'shop', 'install', 'cron', 'api', 'pay', 'public', 'app', 'index', SHOP_MODULE ];
$GLOBALS[ 'app_array' ] = [ 'shop', 'store' ];
}
```

37
docs/common/cron.md Normal file
View File

@@ -0,0 +1,37 @@
# 计划任务
## 特别说明
本项目使用的 是 `yunwuxin/think-cron` 包,该包提供了丰富的功能,包括任务调度、任务执行、任务状态管理等。该包支持多种任务类型,如固定任务、循环任务、一次性任务等,同时支持多种执行周期,如分钟、小时、天、周、月等。
## 计划任务管理
``` php
/**
* 添加计划任务
* @param int $type 任务类型 1.固定任务 2.循环任务
* @param int $period 执行周期
* @param string $name 任务名称
* @param string $event 执行事件
* @param int $execute_time 待执行时间
* @param int $relate_id 关联id
* @param int $period_type 周期类型 0 分钟 1 天 2 周 3 月
*/
public function addCron($type, $period, $name, $event, $execute_time, $relate_id, $period_type = 0)
{
$data = [
'type' => $type,
'period' => $period,
'period_type' => $period_type,
'name' => $name,
'event' => $event,
'execute_time' => $execute_time,
'relate_id' => $relate_id,
'create_time' => time()
];
$res = model('cron')->add($data);
return $this->success($res);
}
```

13
docs/common/layout.md Normal file
View File

@@ -0,0 +1,13 @@
# 布局
## 针对与商户的布局
```html
<!-- 文件位置 -->
src\app\shop\view\layout\base.html
```
- 涉及到的静态资源位置:
- 静态资源文件位置:`src\public\static\`

398
docs/common/stat.md Normal file
View File

@@ -0,0 +1,398 @@
# 统计信息
## 表结构设计
- lucky_stat_shop 店铺统计信息,按天统计
- lucky_stat_shop_hour 店铺统计信息,按小时统计
``` sql
数据源: local_shop_xcx30.5g
架构: shop_mallnew
表: lucky_stat_shop
-- auto-generated definition
create table lucky_stat_shop
(
id int(11) unsigned auto_increment
primary key,
site_id int default 0 not null comment '站点id',
year int default 0 not null comment '年',
month int default 0 not null comment '月',
day int default 0 not null comment '日',
day_time int default 0 not null comment '当日时间',
order_total decimal(10, 2) default 0.00 not null comment '订单金额',
shipping_total decimal(10, 2) default 0.00 not null comment '运费金额',
refund_total decimal(10, 2) default 0.00 not null comment '退款金额',
order_pay_count int default 0 not null comment '订单总数',
goods_pay_count int default 0 not null comment '订单商品总数',
shop_money decimal(10, 2) default 0.00 not null comment '店铺金额',
platform_money decimal(10, 2) default 0.00 not null comment '平台金额',
create_time int default 0 not null comment '创建时间',
modify_time int default 0 not null comment '修改时间',
collect_shop int default 0 not null comment '店铺收藏量',
collect_goods int default 0 not null comment '商品收藏量',
visit_count int default 0 not null comment '浏览量',
order_count int default 0 not null comment '订单量(总)',
goods_count int default 0 not null comment '订单商品量(总)',
add_goods_count int default 0 not null comment '添加商品数',
member_count int default 0 not null comment '会员统计',
order_member_count int default 0 not null comment '下单会员数',
order_refund_count int default 0 not null comment '退款订单数',
order_refund_grand_count decimal(10, 2) default 0.00 not null comment '退款金额',
order_refund_grand_total_money decimal(10, 2) default 0.00 not null comment '累计退款金额',
coupon_member_count int default 0 not null comment '领券会员数量',
member_level_count int default 0 not null comment '超级会员卡销售量',
member_level_total_money decimal(10, 2) default 0.00 not null comment '超级会员卡销售额',
member_level_grand_count int default 0 not null comment '累计超级会员卡销售量',
member_level_grand_total_money decimal(10, 2) default 0.00 not null comment '累计超级会员卡销售额',
member_recharge_count int default 0 not null comment '会员储值总订单量',
member_recharge_grand_count decimal(10, 2) default 0.00 not null comment '累计会员储值总量',
member_recharge_total_money decimal(10, 2) default 0.00 not null comment '会员充值总额',
member_recharge_grand_total_money decimal(10, 2) default 0.00 not null comment '累计会员充值总额',
member_recharge_member_count int default 0 not null comment '储值会员数',
member_giftcard_count int default 0 not null comment '礼品卡订单总量',
member_giftcard_grand_count int default 0 not null comment '累计礼品卡订单总量',
member_giftcard_total_money decimal(10, 2) default 0.00 not null comment '礼品卡订单总额',
h5_visit_count int default 0 not null comment 'h5访问量',
wechat_visit_count int default 0 not null comment 'wechat访问量',
weapp_visit_count int default 0 not null comment 'weapp访问量',
pc_visit_count int default 0 not null comment 'pc访问量',
expected_earnings_total_money decimal(10, 2) default 0.00 not null comment '预计收入',
expenditure_total_money decimal(10, 2) default 0.00 not null comment '总支出',
earnings_total_money decimal(10, 2) default 0.00 not null comment '总收入',
member_withdraw_count int default 0 not null comment '会员提现总量',
member_withdraw_total_money decimal(10, 2) default 0.00 not null comment '会员提现总额',
coupon_count int default 0 not null comment '领券数',
add_coupon_count int default 0 not null comment '新增优惠券',
order_pay_money decimal(10, 2) default 0.00 not null comment '订单实际支付',
add_fenxiao_member_count int default 0 not null comment '新增分销商',
fenxiao_order_total_money decimal(10, 2) default 0.00 not null comment '分销订单总额',
fenxiao_order_count int default 0 not null comment '分销订单总数',
goods_on_type_count int default 0 not null comment '在架商品数',
goods_visited_type_count int default 0 not null comment '被访问商品数(仅详情页浏览数)',
goods_order_type_count int default 0 not null comment '动销商品数',
goods_exposure_count int default 0 not null comment '商品曝光数',
goods_visit_count int default 0 not null comment '商品浏览量',
goods_visit_member_count int default 0 not null comment '商品访客数',
goods_cart_count int default 0 not null comment '加购件数',
goods_order_count decimal(12, 3) default 0.000 not null comment '下单件数',
order_create_money decimal(10, 2) default 0.00 not null comment '订单下单总额',
order_create_count int default 0 not null comment '订单下单量',
balance_deduction decimal(10, 2) unsigned default 0.00 not null comment '余额抵扣总额',
cashier_billing_count int default 0 not null comment '开单数量',
cashier_billing_money decimal(10, 2) default 0.00 not null comment '开单金额',
cashier_buycard_count int default 0 not null comment '办卡数量',
cashier_buycard_money decimal(10, 2) default 0.00 not null comment '办卡金额',
cashier_recharge_count int default 0 not null comment '收银台充值数量',
cashier_recharge_money decimal(10, 2) default 0.00 not null comment '收银台充值金额',
cashier_refund_count int default 0 not null comment '收银台退款数量',
cashier_refund_money decimal(10, 2) default 0.00 not null comment '收银台退款金额',
cashier_order_member_count int default 0 not null comment '收银台下单会员数',
cashier_balance_money decimal(10, 2) default 0.00 not null comment '收银台余额消费金额',
cashier_online_pay_money decimal(10, 2) default 0.00 not null comment '收银台线上金额',
cashier_online_refund_money decimal(10, 2) default 0.00 not null comment '收银台线上退款金额',
cashier_balance_deduction decimal(10, 2) default 0.00 not null comment '门店余额总计'
)
charset = utf8
row_format = DYNAMIC;
create index IDX_ns_stat_shop_day
on lucky_stat_shop (day);
create index IDX_ns_stat_shop_day_time
on lucky_stat_shop (day_time);
create index IDX_ns_stat_shop_month
on lucky_stat_shop (month);
create index IDX_ns_stat_shop_site_id
on lucky_stat_shop (site_id);
create index IDX_ns_stat_shop_year
on lucky_stat_shop (year);
数据源: local_shop_xcx30.5g
架构: shop_mallnew
表: lucky_stat_shop_hour
-- auto-generated definition
create table lucky_stat_shop_hour
(
id int(11) unsigned auto_increment
primary key,
site_id int default 0 not null comment '站点id',
year int default 0 not null comment '年',
month int default 0 not null comment '月',
day int default 0 not null comment '日',
hour int default 0 not null comment '时',
day_time int default 0 not null comment '当日时间',
order_total decimal(10, 2) default 0.00 not null comment '订单金额',
shipping_total decimal(10, 2) default 0.00 not null comment '运费金额',
refund_total decimal(10, 2) default 0.00 not null comment '退款金额',
order_pay_count int default 0 not null comment '订单总数',
goods_pay_count int default 0 not null comment '订单商品总数',
shop_money decimal(10, 2) default 0.00 not null comment '店铺金额',
platform_money decimal(10, 2) default 0.00 not null comment '平台金额',
create_time int default 0 not null comment '创建时间',
modify_time int default 0 not null comment '修改时间',
collect_shop int default 0 not null comment '店铺收藏量',
collect_goods int default 0 not null comment '商品收藏量',
visit_count int default 0 not null comment '浏览量',
order_count int default 0 not null comment '订单量(总)',
goods_count int default 0 not null comment '订单商品量(总)',
add_goods_count int default 0 not null comment '添加商品数',
member_count int default 0 not null comment '会员统计',
order_member_count int default 0 not null comment '下单会员数',
order_refund_count int default 0 not null comment '退款订单数',
order_refund_grand_count decimal(10, 2) default 0.00 not null comment '退款金额',
order_refund_grand_total_money decimal(10, 2) default 0.00 not null comment '累计退款金额',
coupon_member_count int default 0 not null comment '领券会员数量',
member_level_count int default 0 not null comment '超级会员卡销售量',
member_level_total_money decimal(10, 2) default 0.00 not null comment '超级会员卡销售额',
member_level_grand_count int default 0 not null comment '累计超级会员卡销售量',
member_level_grand_total_money decimal(10, 2) default 0.00 not null comment '累计超级会员卡销售额',
member_recharge_count int default 0 not null comment '会员储值总订单量',
member_recharge_grand_count decimal(10, 2) default 0.00 not null comment '累计会员储值总量',
member_recharge_total_money decimal(10, 2) default 0.00 not null comment '会员充值总额',
member_recharge_grand_total_money decimal(10, 2) default 0.00 not null comment '累计会员充值总额',
member_recharge_member_count int default 0 not null comment '储值会员数',
member_giftcard_count int default 0 not null comment '礼品卡订单总量',
member_giftcard_grand_count int default 0 not null comment '累计礼品卡订单总量',
member_giftcard_total_money decimal(10, 2) default 0.00 not null comment '礼品卡订单总额',
h5_visit_count int default 0 not null comment 'h5访问量',
wechat_visit_count int default 0 not null comment 'wechat访问量',
weapp_visit_count int default 0 not null comment 'weapp访问量',
pc_visit_count int default 0 not null comment 'pc访问量',
expected_earnings_total_money decimal(10, 2) default 0.00 not null comment '预计收入',
expenditure_total_money decimal(10, 2) default 0.00 not null comment '总支出',
earnings_total_money decimal(10, 2) default 0.00 not null comment '总收入',
member_withdraw_count int default 0 not null comment '会员提现总量',
member_withdraw_total_money decimal(10, 2) default 0.00 not null comment '会员提现总额',
coupon_count int default 0 not null comment '领券数',
add_coupon_count int default 0 not null comment '新增优惠券',
order_pay_money decimal(10, 2) default 0.00 not null comment '订单实际支付',
add_fenxiao_member_count int default 0 not null comment '新增分销商',
fenxiao_order_total_money decimal(10, 2) default 0.00 not null comment '分销订单总额',
fenxiao_order_count int default 0 not null comment '分销订单总数',
goods_on_type_count int default 0 not null comment '在架商品数',
goods_visited_type_count int default 0 not null comment '被访问商品数(仅详情页浏览数)',
goods_order_type_count int default 0 not null comment '动销商品数',
goods_exposure_count int default 0 not null comment '商品曝光数',
goods_visit_count int default 0 not null comment '商品浏览量',
goods_visit_member_count int default 0 not null comment '商品访客数',
goods_cart_count int default 0 not null comment '加购件数',
goods_order_count decimal(12, 3) default 0.000 not null comment '下单件数',
order_create_money decimal(10, 2) default 0.00 not null comment '订单下单总额',
order_create_count int default 0 not null comment '订单下单量',
balance_deduction decimal(10, 2) default 0.00 not null comment '余额抵扣总额',
cashier_billing_count int default 0 not null comment '开单数量',
cashier_billing_money decimal(10, 2) default 0.00 not null comment '开单金额',
cashier_buycard_count int default 0 not null comment '办卡数量',
cashier_buycard_money decimal(10, 2) default 0.00 not null comment '办卡金额',
cashier_recharge_count int default 0 not null comment '收银台充值数量',
cashier_recharge_money decimal(10, 2) default 0.00 not null comment '收银台充值金额',
cashier_refund_count int default 0 not null comment '收银台退款数量',
cashier_refund_money decimal(10, 2) default 0.00 not null comment '收银台退款金额',
cashier_order_member_count int default 0 not null comment '收银台下单会员数',
cashier_balance_money decimal(10, 2) default 0.00 not null comment '收银台余额消费金额',
cashier_online_pay_money decimal(10, 2) default 0.00 not null comment '收银台线上金额',
cashier_online_refund_money decimal(10, 2) default 0.00 not null comment '收银台线上退款金额',
cashier_balance_deduction decimal(10, 2) default 0.00 not null comment '门店余额总计'
)
charset = utf8
row_format = DYNAMIC;
create index IDX_ns_stat_shop_hour_day
on lucky_stat_shop_hour (day);
create index IDX_ns_stat_shop_hour_day_time
on lucky_stat_shop_hour (day_time);
create index IDX_ns_stat_shop_hour_hour
on lucky_stat_shop_hour (hour);
create index IDX_ns_stat_shop_hour_month
on lucky_stat_shop_hour (month);
create index IDX_ns_stat_shop_hour_site_id
on lucky_stat_shop_hour (site_id);
create index IDX_ns_stat_shop_hour_year
on lucky_stat_shop_hour (year);
```
## 数据关联
### 1. 数据的写入 addShopStat
与添加店铺统计数据有关的函数addShopStat 在三个文件中,都存在
- File1: `src\app\model\stat\StatShop.php`
- File2: `src\app\model\system\Stat.php`
实现细节该File1文件中的 `addShopStat` 函数,从自己函数 `getStatData` 获得整理后的数据,然后将数据传输给 File2 `src/app/model/stat/StatShop.php` 的 `addShopStat` 函数
```php
// src/app/model/stat/StatShop.php
public function addShopStat($data)
{
$carbon = Carbon::now();
$dir = __UPLOAD__.'/stat/stat_shop/';
if (!is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) {
return $this->error(sprintf('Directory "%s" was not created', $dir));
}
$filename = $dir.$carbon->year.'_'.$carbon->month.'_'.$carbon->day.'_'.$carbon->second.'_'.unique_random().'.json';
$stat_extend = new Stat($filename, 'stat_shop',$data['site_id']);
$stat_extend->handleData($data);//写入文件
//增加当天时统计
$this->addShopHourStat($data, $carbon);
return $this->success();
}
```
分析addShopStat是将统计记录写入到文件中为快速存储数据及留痕提供数据支持。
### 2. 数据的提取 cronShopStat 计划任务统计数据处理
文件src/app/model/stat/StatShop.php
`cronShopStat`,是将统计记录从文件中提取出来,然后写入到数据库中,为快速存储数据及留痕提供数据支持。
``` php
// src\app\model\stat\StatShop.php
/**
* 从stat_shop目录下读取所有文件将数据处理后写入数据表中
* 处理完每个文件后,删除文件
*/
public function cronShopStat()
{
$path = __UPLOAD__.'/stat/stat_shop';
if(!is_dir($path)) return;
$result = $this->scanFile($path);
if(empty($result)) return;
try {
$json_array = [];
foreach ($result as $key => $val){
$stat_extend = new Stat($path.'/'.$val, 'stat_shop');
$json_array[] = $stat_extend->load();
unlink($path.'/'.$val); // 处理完文件后,删除文件
}
$data_array = [];
foreach ($json_array as $json_k => $json_v){
$k = $json_v['year'].'_'.$json_v['month'].'_'.$json_v['day'];
if (isset($data_array[$k])){
foreach ($data_array[$k] as $data_k => $data_v){
if($data_k != 'site_id' && $data_k != 'year' && $data_k != 'month' && $data_k != 'day' && $data_k != 'day_time'){
if ($json_v[$data_k] > 0) {
$data_array[$k][$data_k] += $json_v[$data_k];
} else if ($json_v[$data_k] < 0) {
$data_array[$k][$data_k] -= abs($json_v[$data_k]);
}
}
}
}else{
$data_array[$k] = $json_v;
}
}
Log::write(time().'stat_shop_'.json_encode($data_array));
$system_stat = new \app\model\system\Stat();
foreach ($data_array as $json_k => $json_v){
$system_stat->addStatShopModel($json_v);
}
} catch (\Exception $e) {
}
}
```
1. 从函数的开头可以看出,该函数用于计划任务。
2. 这个函数将被 `src\app\event\stat\CronStatShop.php` 中的 handle() 方法调用.
3. 事件的绑定在 `src\app\event.php` 文件中定义。
```php
//店铺统计更新(按时)
'CronStatShopHour' => [
'app\event\stat\CronStatShopHour'
],
```
说明需要通过ThinkPHP的Event的手动触发或者自动触发来处理
方式1手动触发最常用
```php
// 通过 event()助手函数或 Event门面触发
// 在控制器、模型或任何地方触发事件
event('UserLogin', $user);
// 等价于
use think\facade\Event;
Event::trigger('UserLogin', $user);
```
方式2自动触发通过事件类
定义事件类并直接触发:
```php
// 定义事件类
namespace app\event;
class UserLogin
{
public function __construct(public $user) {}
}
// 触发事件
$event = new \app\event\UserLogin($user);
event($event);
```
### 3. 数据的展示调用
## 数据获取
数据对应的文件:`src\app\model\system\Stat.php`
1. 获取天统计表统计字段getStatField
2. 获取时统计表统计字段getStatHourField
3. 获取商品销量排行榜getGoodsSaleNumRankingList
## 应用场景
### 优惠券的统计
调用者:`src\addon\coupon\model\CouponStat.php`
被调用者:`src\app\model\system\Stat.php`
### 分销订单的统计
调用者:`src\addon\fenxiao\model\FenxiaoStat.php`
被调用者:`src\app\model\system\Stat.php`
### 虚拟商品的统计
调用者:`src\addon\virtualcard\model\VirtualGoods.php`
被调用者:`src\app\model\system\Stat.php`
### 商品收藏的统计
调用者:`src\app\model\goods\GoodsCollect.php`
被调用者:`src\app\model\system\Stat.php`
### 购物车的统计
调用者:`src\app\model\stat\GoodsCartStat.php`
被调用者:`src\app\model\system\Stat.php`

428
docs/readme.md Normal file
View File

@@ -0,0 +1,428 @@
# 说明
## 基本概念
店铺和门店的关系可以从不同角度来理解,让我为您详细解释:
## 🏪 **店铺 vs 门店:概念解析**
### **1. 从层级关系理解**
#### **店铺(总店/品牌店)**
```mermaid
graph TD
A[品牌总部/总店] --> B1[门店A]
A --> B2[门店B]
A --> B3[门店C]
A --> B4[线上店铺]
B1 --> C1[分店A-1]
B1 --> C2[分店A-2]
B2 --> C3[分店B-1]
```
**店铺特征:**
- **管理层面**:品牌、总部、总店的概念
- **业务范围**:可能包含多个物理门店和线上渠道
- **功能定位**:战略决策、品牌管理、供应链控制
#### **门店(分店/实体店)**
```mermaid
graph LR
A[门店] --> B[具体营业场所]
A --> C[固定地址]
A --> D[面对面服务]
```
**门店特征:**
- **物理存在**:有具体的营业场所和地址
- **服务范围**:服务特定区域客户
- **运营独立**:相对独立的日常运营
### **2. 从业务模式区分**
| 维度 | 店铺Shop | 门店Store |
|------|-------------|--------------|
| **存在形式** | 可虚拟可实体 | 必须是实体 |
| **数量关系** | 1个店铺可有多门店 | 1个门店属于1个店铺 |
| **管理范围** | 品牌、战略、标准 | 日常运营、客户服务 |
| **系统关系** | 总部系统、ERP | POS系统、库存管理 |
### **3. 实际业务场景**
#### **场景1连锁品牌**
```
店铺(总店)
├── 北京门店(朝阳区店)
├── 上海门店(浦东店)
├── 广州门店(天河店)
└── 线上官方店铺
```
#### **场景2电商+实体结合**
```
店铺(品牌旗舰店)
├── 天猫官方店铺(线上)
├── 京东官方店铺(线上)
├── 线下体验门店(实体)
└── 微信小程序店铺(线上)
```
#### **场景3单店经营**
```
店铺 = 门店(合二为一)
└── 既是品牌也是具体营业点
```
## 💼 **在智能客服系统中的数据建模**
### **1. 数据库设计**
```sql
-- 店铺表(总店/品牌)
CREATE TABLE shops (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL, -- 店铺名称
brand_name VARCHAR(100), -- 品牌名称
type ENUM('single', 'chain', 'online'), -- 店铺类型
status ENUM('active', 'inactive'),
created_at TIMESTAMP
);
-- 门店表(分店/实体店)
CREATE TABLE stores (
id INT PRIMARY KEY AUTO_INCREMENT,
shop_id INT, -- 所属店铺
name VARCHAR(100) NOT NULL, -- 门店名称
address TEXT, -- 具体地址
phone VARCHAR(20),
manager_id INT, -- 店长
business_hours JSON, -- 营业时间
latitude DECIMAL(10, 8), -- 纬度
longitude DECIMAL(11, 8), -- 经度
status ENUM('open', 'closed', 'maintenance'),
FOREIGN KEY (shop_id) REFERENCES shops(id)
);
-- 员工表(关联门店)
CREATE TABLE staff (
id INT PRIMARY KEY AUTO_INCREMENT,
store_id INT, -- 所属门店
name VARCHAR(50),
role ENUM('manager', 'assistant', 'cashier'),
phone VARCHAR(20),
FOREIGN KEY (store_id) REFERENCES stores(id)
);
```
### **2. 业务逻辑实现**
```javascript
// 店铺管理类
class ShopManager {
constructor() {
this.shops = new Map();
this.stores = new Map();
}
// 创建店铺
createShop(shopData) {
const shop = {
id: this.generateId(),
name: shopData.name,
brand: shopData.brand,
type: shopData.type || 'single',
stores: [], // 下属门店
createdAt: new Date(),
status: 'active'
};
this.shops.set(shop.id, shop);
return shop;
}
// 添加门店到店铺
addStoreToShop(shopId, storeData) {
const shop = this.shops.get(shopId);
if (!shop) throw new Error('店铺不存在');
const store = {
id: this.generateId(),
shopId: shopId,
name: storeData.name,
address: storeData.address,
contact: storeData.contact,
coordinates: storeData.coordinates,
businessHours: storeData.businessHours,
staff: [],
createdAt: new Date()
};
shop.stores.push(store.id);
this.stores.set(store.id, store);
return store;
}
// 根据位置查找最近门店
findNearestStore(shopId, userLocation) {
const shop = this.shops.get(shopId);
if (!shop) return null;
let nearestStore = null;
let minDistance = Infinity;
shop.stores.forEach(storeId => {
const store = this.stores.get(storeId);
if (store && store.status === 'open') {
const distance = this.calculateDistance(
userLocation,
store.coordinates
);
if (distance < minDistance) {
minDistance = distance;
nearestStore = store;
}
}
});
return nearestStore;
}
// 计算距离(简化版)
calculateDistance(loc1, loc2) {
const R = 6371; // 地球半径公里
const dLat = this.deg2rad(loc2.lat - loc1.lat);
const dLon = this.deg2rad(loc2.lon - loc1.lon);
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(this.deg2rad(loc1.lat)) * Math.cos(this.deg2rad(loc2.lat)) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
deg2rad(deg) {
return deg * (Math.PI/180);
}
}
```
### **3. 在智能客服中的对话逻辑**
```javascript
// 店铺门店相关的对话处理
class StoreDialogHandler {
constructor(shopManager) {
this.shopManager = shopManager;
}
// 处理用户查询
handleStoreQuery(userMessage, userContext) {
const intent = this.detectIntent(userMessage);
switch(intent) {
case 'find_nearest':
return this.handleFindNearest(userMessage, userContext);
case 'store_hours':
return this.handleStoreHours(userMessage, userContext);
case 'store_contact':
return this.handleStoreContact(userMessage, userContext);
case 'all_stores':
return this.handleAllStores(userMessage, userContext);
default:
return this.handleGeneralQuery(userMessage, userContext);
}
}
// 查找最近门店
handleFindNearest(userMessage, userContext) {
const location = this.extractLocation(userMessage);
const shopId = userContext.currentShopId;
if (!location) {
return {
type: 'request_location',
message: '请问您当前在哪个位置?我可以帮您查找最近的门店。',
options: ['使用当前位置', '手动输入地址']
};
}
const nearestStore = this.shopManager.findNearestStore(shopId, location);
if (nearestStore) {
return {
type: 'store_info',
message: `离您最近的门店是:${nearestStore.name}`,
data: {
store: nearestStore,
distance: this.calculateDistance(location, nearestStore.coordinates),
estimatedTime: this.estimateTravelTime(nearestStore.coordinates, location)
},
actions: [
{ text: '查看详情', action: 'show_store_detail' },
{ text: '导航前往', action: 'navigate_to_store' },
{ text: '联系门店', action: 'contact_store' }
]
};
} else {
return {
type: 'no_store_found',
message: '在您附近没有找到我们的门店,建议您尝试线上服务或查看其他区域的门店。',
alternatives: this.findAlternativeStores(shopId)
};
}
}
// 处理营业时间查询
handleStoreHours(userMessage, userContext) {
const storeName = this.extractStoreName(userMessage);
const store = this.findStoreByName(storeName, userContext.currentShopId);
if (store) {
const hours = store.businessHours;
const currentStatus = this.getCurrentStatus(store);
return {
type: 'business_hours',
message: `${store.name}的营业时间:${this.formatBusinessHours(hours)}`,
data: {
store: store,
currentStatus: currentStatus,
todayHours: this.getTodayHours(hours)
}
};
}
}
}
```
### **4. 响应消息模板**
```javascript
// 店铺门店信息响应模板
const StoreResponseTemplates = {
// 门店列表响应
storeList: (stores, options = {}) => {
return {
type: 'store_list',
layout: 'card',
title: options.title || '我们的门店',
items: stores.map(store => ({
title: store.name,
description: store.address,
image: store.image,
meta: {
distance: store.distance ? `${store.distance}km` : '',
status: store.status === 'open' ? '营业中' : '休息中',
hours: store.businessHours ? '查看营业时间' : ''
},
actions: [
{ text: '导航', action: 'navigate', data: { storeId: store.id } },
{ text: '电话', action: 'call', data: { phone: store.phone } },
{ text: '详情', action: 'detail', data: { storeId: store.id } }
]
})),
quickReplies: [
'找最近的门店',
'查看所有门店',
'联系客服'
]
};
},
// 单个门店详情
storeDetail: (store) => {
return {
type: 'store_detail',
layout: 'detail',
title: store.name,
sections: [
{
title: '📍 地址',
content: store.address,
action: { type: 'map', data: store.coordinates }
},
{
title: '📞 联系方式',
content: store.phone,
action: { type: 'call', data: store.phone }
},
{
title: '🕒 营业时间',
content: formatBusinessHours(store.businessHours),
action: { type: 'hours', data: store.businessHours }
},
{
title: '👥 门店服务',
content: store.services.join('、'),
action: { type: 'services', data: store.services }
}
],
actions: [
{ text: '分享门店', action: 'share' },
{ text: '收藏门店', action: 'favorite' },
{ text: '投诉建议', action: 'feedback' }
]
};
},
// 营业时间响应
businessHours: (store, currentStatus) => {
return {
type: 'business_hours',
message: `**${store.name}**\n\n` +
`当前状态:**${currentStatus}**\n\n` +
`营业时间:\n${formatBusinessHours(store.businessHours)}`,
quickActions: [
{ text: '明天营业时间', action: 'tomorrow_hours' },
{ text: '节假日安排', action: 'holiday_hours' },
{ text: '联系确认', action: 'confirm_hours' }
]
};
}
};
// 工具函数
function formatBusinessHours(hours) {
if (!hours) return '暂无信息';
const days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
return days.map((day, index) => {
const dayHours = hours[index] || hours['default'];
return `${day}: ${dayHours || '休息'}`;
}).join('\n');
}
```
## 🎯 **总结:店铺与门店的关系**
### **核心关系**
1. **包含关系**店铺包含门店1:N
2. **品牌关系**:店铺是品牌,门店是落地实体
3. **管理关系**:店铺管理门店的标准和运营
### **在智能客服中的应用**
- **用户说"我要找店铺"** → 理解为品牌/总店概念
- **用户说"找最近的门店"** → 理解为具体营业点
- **用户说"你们店在哪里"** → 需要区分是指品牌还是具体门店
### **数据处理建议**
```javascript
// 在客服系统中这样区分:
const customerQuery = {
"找苹果店": { type: "shop", brand: "apple" },
"找朝阳区的苹果店": { type: "store", brand: "apple", location: "朝阳区" },
"线上店铺": { type: "online_shop" },
"实体门店": { type: "physical_store" }
};
```
这样的区分让智能客服能够更精准地理解用户意图,提供更准确的店铺/门店信息服务。