# 开发者工作台
> 开发者服务统一工作台，切换开发配置、接口文档、小程序、离线下载、CDN 与插件等区块。

## 1. 页面信息

- 网页地址：https://9kvr.cn/user/develop/workbench
- 所属模块：用户中心 · 开发者服务
- 页面用途：开发者服务的统一工作台。左侧（移动端为顶部标签）可切换六个区块：开发初始配置、接口文档服务、小程序工作台、全景离线下载、自定义 CDN 素材、全景插件服务。开发者配置未完成时切换区块会被拦截；自定义 CDN 素材需要开发版及以上会员；接口文档服务中的 API 读写能力需要企业版及以上会员。
- 适用用户：已完成或正在完成开发者配置的用户
- 访问条件：需登录后访问（API 读写能力需企业版及以上会员）

## 2. 功能说明

- 开发初始配置：配置开发者应用信息与可用平台
- 接口文档服务：查看接口文档
- 小程序工作台：在微信 / 快手 / 百度 / 字节 / QQ 等平台间切换
- 全景离线下载：离线部署相关能力
- 自定义 CDN 素材（需开发版及以上会员）
- 全景插件服务
- 支持通过参数指定初始区块、预置作品与下载来源

## 3. 业务说明

### 权限要求

- 开发初始配置：需登录
- 自定义 CDN 素材：需开发版及以上会员

### 配置前置

在完成开发者基本信息配置前，切换其他区块会被提示「请先配置完成基本信息后再试」。

### 平台限制

小程序工作台可用平台取决于账号允许的平台范围；开发版及以上会员不受平台校验限制。

### 接口与 API 的最低会员档位

「接口文档服务」中的 API 能力按会员版本开放：通过 API 获取作品信息（读）、管理全景作品（写）、提交素材制作全景、接收制作结果通知等接口，均在**企业版及以上**版本开放（开发版不含）。各版本具体权益以 VIP 会员服务页的权益矩阵实时口径为准。

- 读接口（获取作品信息）：企业版及以上开放
- 写接口（管理全景作品、提交制作、接收结果通知）：企业版及以上开放
- 开发版不含上述 API 读写能力，需升级到企业版及以上

<!-- public-api:start -->

### 开发者公开 API 参考（10 项）

以下为面向开发者公开的正式接口能力，接口地址、请求参数、返回字段与多语言调用示例均来自官方公开文档。运行示例前请把 `EXAMPLE_API_KEY` / `YOUR_WORK_ID` / `YOUR_MEDIA_ID` 等占位符替换为你自己的合法值；POST 接口会产生写入副作用，请勿使用他人密钥或他人作品 ID 反复调用。

#### 1. 使用Api获取作品列表（getVrList()）

##### 接口地址
```
GET https://develop.9kvr.cn/api/develop/listd.html?key=apiKey
```

##### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者API密钥 |

##### 响应示例
```json
{
  "info": {
    "total": 310,
    "per_page": 12,
    "current_page": 1,
    "last_page": 26,
    "data": [
      {
        "work_name": "示例作品",
        "work_profile": "",
        "work_thumb": "https://cdn.example.com/example-pano.jpg",
        "work_createtime": "2025-07-15 22:53:05",
        "work_status": 2,
        "work_makes": 2,
        "work_type": 1,
        "work_viewid": "YOUR_WORK_ID",
        "work_looks": 6,
        "mark_douyin_title": null,
        "mark_kuaishou_title": null,
        "mark_recommend": null,
        "mark_boutique": null,
        "work_xls_id": 1,
        "location_name": null,
        "location_address": null,
        "location_latitude": null,
        "location_longitude": null,
        "work_play": "https://develop.9kvr.cn/tour/index?id=YOUR_WORK_ID&key=EXAMPLE_API_KEY",
        "work_thumb_last": "https://cdn.example.com/example-pano.jpg",
        "mount_title": "示例作品"
      }
    ]
  },
  "msg": "获取成功",
  "code": 1,
  "sparrow": 0,
  "time": 104,
  "ip": "127.0.0.1",
  "version": 2
}
```

##### 响应参数说明

###### 分页信息 (info)
| 参数名 | 类型 | 说明 |
|--------|------|------|
| total | number | 总记录数 |
| per_page | number | 每页显示数量 |
| current_page | number | 当前页码 |
| last_page | number | 最后一页页码 |
| data | array | 作品数据列表 |

###### 作品信息 (data)
| 参数名 | 类型 | 说明 |
|--------|------|------|
| work_name | string | 作品名称 |
| work_profile | string | 作品描述 |
| work_thumb | string | 作品缩略图 |
| work_createtime | string | 作品创建时间 |
| work_status | number | 作品状态 |
| work_makes | number | 制作状态 |
| work_type | number | 作品类型 |
| work_viewid | string | 作品唯一ID |
| work_looks | number | 浏览次数 |
| mark_douyin_title | string | 抖音标题标记 |
| mark_kuaishou_title | string | 快手标题标记 |
| mark_recommend | string | 推荐标记 |
| mark_boutique | string | 精品标记 |
| work_xls_id | number | 作品关联ID |
| location_name | string | 位置名称 |
| location_address | string | 位置地址 |
| location_latitude | string | 纬度坐标 |
| location_longitude | string | 经度坐标 |
| work_play | string | 作品播放链接 |
| work_thumb_last | string | 最后缩略图 |
| mount_title | string | 挂载标题 |

###### 响应状态
| 参数名 | 类型 | 说明 |
|--------|------|------|
| msg | string | 响应消息 |
| code | number | 响应状态码 (1: 成功, 0: 失败) |
| sparrow | number | 系统标识 |
| time | number | 响应时间(毫秒) |
| ip | string | 请求IP地址 |
| version | number | API版本号 |

##### 状态码说明
| 状态码 | 说明 |
|--------|------|
| 1 | 成功 |
| 0 | 失败 |

##### 使用说明
1. 请确保已获取有效的API密钥
2. 该接口返回用户的所有全景作品列表
3. 支持分页查询，默认每页12条记录
4. 返回数据包含完整的分页信息和作品详情
5. 作品播放链接可直接用于webview打开

#### 2. 使用Api获取作品详情（getVrDetail()）

##### 接口地址
```
GET https://develop.9kvr.cn/api/develop/info.html?key=apiKey&id=全景ID
```

##### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者API密钥 |
| id | string | 是 | 全景作品ID |

##### 响应示例
```json
{
  "info": {
    "work_name": "示例作品",
    "work_profile": "",
    "work_thumb": "https://cdn.example.com/example-pano.jpg",
    "work_createtime": "2025-07-15 22:53:05",
    "work_status": 2,
    "work_makes": 2,
    "work_type": 1,
    "work_viewid": "YOUR_WORK_ID",
    "work_looks": 6,
    "mark_douyin_title": null,
    "mark_kuaishou_title": null,
    "mark_recommend": null,
    "mark_boutique": null,
    "work_xls_id": 1,
    "location_name": null,
    "location_address": null,
    "location_latitude": null,
    "location_longitude": null,
    "work_play": "https://develop.9kvr.cn/tour/index?id=YOUR_WORK_ID&key=EXAMPLE_API_KEY",
    "work_thumb_last": "https://cdn.example.com/example-pano.jpg",
    "mount_title": "示例作品"
  },
  "msg": "获取成功",
  "code": 1,
  "sparrow": 0,
  "time": 28,
  "ip": "127.0.0.1",
  "version": 2
}
```

##### 响应参数说明

###### 作品信息 (info)
| 参数名 | 类型 | 说明 |
|--------|------|------|
| work_name | string | 作品名称 |
| work_profile | string | 作品描述 |
| work_thumb | string | 作品缩略图 |
| work_createtime | string | 作品创建时间 |
| work_status | number | 作品状态 |
| work_makes | number | 制作状态 |
| work_type | number | 作品类型 |
| work_viewid | string | 作品唯一ID |
| work_looks | number | 浏览次数 |
| mark_douyin_title | string | 抖音标题标记 |
| mark_kuaishou_title | string | 快手标题标记 |
| mark_recommend | string | 推荐标记 |
| mark_boutique | string | 精品标记 |
| work_xls_id | number | 作品关联ID |
| location_name | string | 位置名称 |
| location_address | string | 位置地址 |
| location_latitude | string | 纬度坐标 |
| location_longitude | string | 经度坐标 |
| work_play | string | 作品播放链接 |
| work_thumb_last | string | 最后缩略图 |
| mount_title | string | 挂载标题 |

###### 响应状态
| 参数名 | 类型 | 说明 |
|--------|------|------|
| msg | string | 响应消息 |
| code | number | 响应状态码 (1: 成功, 0: 失败) |
| sparrow | number | 系统标识 |
| time | number | 响应时间(毫秒) |
| ip | string | 请求IP地址 |
| version | number | API版本号 |

##### 状态码说明
| 状态码 | 说明 |
|--------|------|
| 1 | 成功 |
| 0 | 失败 |

##### 使用说明
1. 通过作品ID获取单个全景作品的详细信息
2. 包含作品的完整信息，包括基本信息、状态、位置、播放链接等
3. 可用于小程序或H5页面展示全景内容
4. 作品播放链接可直接用于webview打开
5. 支持获取作品的地理位置信息（如果已设置）

#### 3. Webview打开单个全景链接（openWebview()）

##### 链接格式
```
https://develop.9kvr.cn/tour/index?id=作品ID&key=apiKey
```

##### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | string | 是 | 全景作品ID |
| key | string | 是 | 开发者API密钥 |

##### 使用场景
1. **小程序Webview组件**：在小程序中使用webview组件打开全景页面
2. **H5页面跳转**：在H5页面中直接跳转到全景播放页面
3. **分享链接**：生成可分享的全景查看链接

##### 注意事项
- 请勿直接在小程序中复制H5链接使用
- 必须使用上述格式的链接作为webview的src
- 确保API密钥有效且具有相应权限
- 建议在小程序webview中设置合适的宽高比例

##### 示例代码
```javascript
// 小程序webview使用示例
<webview src="https://develop.9kvr.cn/tour/index?id=123456&key=your_api_key"></webview>
```

#### 4. 使用API制作VR全景（createVrPanorama()）

##### 接口地址
```
POST https://develop.9kvr.cn/api/develop/makeTour.html?key=apiKey
```

##### 请求参数

###### 基础参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者KEY(在开发者配置页面获取) |
| notifier | number | 否 | 是否接受微信模板消息通知, 1 接收, 0 不接收 |

###### 作品信息 (work) - json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| work_name | string | 是 | 作品名称 |
| work_profile | string | 否 | 作品介绍 |
| work_private | number | 否 | 是否私有, 1 公开, 0 私有 |
| work_password | string | 否 | 访问密码, 必须设置为私有才可设置访问密码 |

###### 素材信息 (media)- json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| imgs_location | string | 是 | VR素材CDN地址 |
| imgs_filename | string | 是 | VR素材名称(最大支持15个字符) |
| imgs_description | string | 否 | 素材介绍(最大支持60个字符) |

> **注意**: media数组最大支持20组素材

###### 配置信息 (config) - json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| set_more_menu | number | 否 | 显示更多菜单按钮, 1显示(默认), 0 不显示 |
| set_hide_right | number | 否 | 隐藏右侧整体导航条, 1显示(默认), 0 不显示 |
| set_title_mask | number | 否 | 芊云全景版权小尾巴, 1显示(默认), 0 不显示 |
| work_music | string | 否 | 作品全局背景音乐(仅限mp3文件) |

##### 入参示例

```json
{
  "work": {
    "work_name": "示例作品",
    "work_profile": "作品介绍",
    "work_private": "是否私有,1 公开 0私有",
    "work_password": "访问密码, 必须设置为私有才可设置访问密码"
  },
  "key": "开发者KEY(在开发者配置页面获取)",
  "media": [
    {
      "imgs_location": "VR素材CDN地址",
      "imgs_filename": "VR素材名称",
      "imgs_description": "素材介绍"
    },
    {
      "imgs_location": "... media数组最大支持20组素材",
      "imgs_filename": "... VR素材名称(名称最大支持15个字符)",
      "imgs_description": "... VR素材介绍(名称最大支持60个字符)"
    }
  ],
  "notifier": "是否接受微信模板消息通知, 1 接收, 0 不接收",
  "config": {
    "set_more_menu": "显示更多菜单按钮; 1显示(默认), 0 不显示",
    "set_hide_right": "隐藏右侧整体导航条; 1显示(默认), 0 不显示",
    "set_title_mask": "芊云全景版权小尾巴: 1显示(默认), 0 不显示",
    "work_music": "作品全局背景音乐(仅限mp3文件)"
  }
}
```

##### 调用示例代码

###### Node.js 示例

```javascript
const axios = require('axios');
const FormData = require('form-data');

async function makeTour() {
  const apiKey = 'your_api_key_here';
  const url = `https://develop.9kvr.cn/api/develop/makeTour.html?key=${apiKey}`;
  
  const formData = new FormData();
  
  // 作品信息
  const work = {
    work_name: '我的VR作品',
    work_profile: '这是一个测试VR作品',
    work_private: '1'
  };
  formData.append('work', JSON.stringify(work));
  
  // 素材信息
  const media = [
    {
      imgs_location: 'https://cdn.example.com/example-pano.jpg',
      imgs_filename: '全景图1',
      imgs_description: '第一个全景场景'
    },
    {
      imgs_location: 'https://cdn.example.com/pano2.jpg',
      imgs_filename: '全景图2',
      imgs_description: '第二个全景场景'
    }
  ];
  formData.append('media', JSON.stringify(media));
  
  // 通知设置
  formData.append('notifier', '1');
  
  // 配置信息
  const config = {
    set_more_menu: '1',
    set_hide_right: '1',
    set_title_mask: '1'
  };
  formData.append('config', JSON.stringify(config));
  
  try {
    const response = await axios.post(url, formData, {
      headers: {
        ...formData.getHeaders(),
        'Origin': 'https://your-domain.com'
      }
    });
    
    console.log('成功:', response.data);
    return response.data;
  } catch (error) {
    console.error('失败:', error.response ? error.response.data : error.message);
    throw error;
  }
}

makeTour();
```

###### PHP 示例

```php
<?php

function makeTour() {
    $apiKey = 'your_api_key_here';
    $url = 'https://develop.9kvr.cn/api/develop/makeTour.html?key=' . $apiKey;
    
    // 准备表单数据
    $postData = [
        // 作品信息
        'work' => json_encode([
            'work_name' => '我的VR作品',
            'work_profile' => '这是一个测试VR作品',
            'work_private' => '1'
        ]),
        // 素材信息
        'media' => json_encode([
            [
                'imgs_location' => 'https://cdn.example.com/example-pano.jpg',
                'imgs_filename' => '全景图1',
                'imgs_description' => '第一个全景场景'
            ],
            [
                'imgs_location' => 'https://cdn.example.com/pano2.jpg',
                'imgs_filename' => '全景图2',
                'imgs_description' => '第二个全景场景'
            ]
        ]),
        // 通知设置
        'notifier' => '1',
        // 配置信息
        'config' => json_encode([
            'set_more_menu' => '1',
            'set_hide_right' => '1',
            'set_title_mask' => '1'
        ])
    ];
    
    // 初始化 cURL
    $ch = curl_init();
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query($postData),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Origin: https://your-domain.com',
            'Content-Type: application/x-www-form-urlencoded'
        ]
    ]);
    
    // 执行请求
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    if (curl_errno($ch)) {
        echo '请求错误: ' . curl_error($ch);
        curl_close($ch);
        return false;
    }
    
    curl_close($ch);
    
    // 解析响应
    $result = json_decode($response, true);
    
    if ($httpCode === 200) {
        echo "成功: \n";
        print_r($result);
        return $result;
    } else {
        echo "失败: HTTP {$httpCode}\n";
        print_r($result);
        return false;
    }
}

makeTour();
?>
```

###### Python 示例

```python
import requests

def make_tour():
    import json
    
    api_key = 'your_api_key_here'
    url = f'https://develop.9kvr.cn/api/develop/makeTour.html?key={api_key}'
    
    # 准备表单数据
    data = {
        # 作品信息
        'work': json.dumps({
            'work_name': '我的VR作品',
            'work_profile': '这是一个测试VR作品',
            'work_private': '1'
        }),
        # 素材信息
        'media': json.dumps([
            {
                'imgs_location': 'https://cdn.example.com/example-pano.jpg',
                'imgs_filename': '全景图1',
                'imgs_description': '第一个全景场景'
            },
            {
                'imgs_location': 'https://cdn.example.com/pano2.jpg',
                'imgs_filename': '全景图2',
                'imgs_description': '第二个全景场景'
            }
        ]),
        # 通知设置
        'notifier': '1',
        # 配置信息
        'config': json.dumps({
            'set_more_menu': '1',
            'set_hide_right': '1',
            'set_title_mask': '1'
        })
    }
    
    # 设置请求头
    headers = {
        'Origin': 'https://your-domain.com'
    }
    
    try:
        # 发送 POST 请求
        response = requests.post(url, data=data, headers=headers)
        response.raise_for_status()
        
        # 解析响应
        result = response.json()
        print('成功:', result)
        return result
        
    except requests.exceptions.RequestException as e:
        print(f'请求失败: {e}')
        if hasattr(e, 'response') and e.response is not None:
            print('响应内容:', e.response.text)
        return None

if __name__ == '__main__':
    make_tour()
```

###### Java 示例

```java
import com.google.gson.Gson;
import okhttp3.*;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MakeTourExample {
    
    private static final String API_KEY = "your_api_key_here";
    private static final String API_URL = "https://develop.9kvr.cn/api/develop/makeTour.html?key=" + API_KEY;
    
    public static void main(String[] args) {
        try {
            makeTour();
        } catch (IOException e) {
            System.err.println("请求失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static void makeTour() throws IOException {
        Gson gson = new Gson();
        
        // 作品信息
        Map<String, String> work = new HashMap<>();
        work.put("work_name", "我的VR作品");
        work.put("work_profile", "这是一个测试VR作品");
        work.put("work_private", "1");
        String workJson = gson.toJson(work);
        
        // 素材信息
        Map<String, String> media1 = new HashMap<>();
        media1.put("imgs_location", "https://cdn.example.com/example-pano.jpg");
        media1.put("imgs_filename", "全景图1");
        media1.put("imgs_description", "第一个全景场景");
        
        Map<String, String> media2 = new HashMap<>();
        media2.put("imgs_location", "https://cdn.example.com/pano2.jpg");
        media2.put("imgs_filename", "全景图2");
        media2.put("imgs_description", "第二个全景场景");
        
        List<Map<String, String>> mediaList = Arrays.asList(media1, media2);
        String mediaJson = gson.toJson(mediaList);
        
        // 配置信息
        Map<String, String> config = new HashMap<>();
        config.put("set_more_menu", "1");
        config.put("set_hide_right", "1");
        config.put("set_title_mask", "1");
        String configJson = gson.toJson(config);
        
        // 构建表单数据
        RequestBody formBody = new FormBody.Builder()
                .add("work", workJson)
                .add("media", mediaJson)
                .add("notifier", "1")
                .add("config", configJson)
                .build();
        
        // 创建请求
        Request request = new Request.Builder()
                .url(API_URL)
                .post(formBody)
                .addHeader("Origin", "https://your-domain.com")
                .build();
        
        // 发送请求
        OkHttpClient client = new OkHttpClient();
        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful() && response.body() != null) {
                String responseBody = response.body().string();
                System.out.println("成功: " + responseBody);
                
                // 解析响应
                @SuppressWarnings("unchecked")
                Map<String, Object> result = gson.fromJson(responseBody, Map.class);
                System.out.println("返回码: " + result.get("code"));
                System.out.println("消息: " + result.get("msg"));
            } else {
                System.err.println("请求失败: HTTP " + response.code());
                if (response.body() != null) {
                    System.err.println("响应内容: " + response.body().string());
                }
            }
        }
    }
}
```

**依赖配置 (Maven)**:
```xml
<dependencies>
    <!-- OkHttp -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    
    <!-- Gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>
```

**依赖配置 (Gradle)**:
```gradle
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.google.code.gson:gson:2.10.1'
}
```

##### 返回示例

###### 成功响应
```json
{
  "info": {
    "work_id": "作品Id"
  },
  "msg": "投递成功",
  "code": 1
}
```

###### 失败响应
```json
{
  "info": {},
  "msg": "投递失败的原因",
  "code": 0
}
```

##### 回调通知

###### 成功回调
```json
{
  "info": {
    "work_id": "作品Id",
    "media": {
      "imgs_filename": "素材名称",
      "imgs_thumb": "素材预览图",
      "imgs_id": "素材ID",
      "imgs_web_link": "素材预览链接",
      "imgs_mini_link": "小程序预览链接"
    },
    "link": {
      "web": "网页链接",
      "mini": "小程序链接"
    },
    "work": {
      "work_thumb": "全景作品封面",
      "work_uid": "开发者 UID",
      "work_viewid": "YOUR_WORK_ID"
    }
  },
  "msg": "全景制作完成",
  "code": 1
}
```

###### 失败回调
```json
{
  "info": {},
  "msg": "全景制作失败的原因",
  "code": 0
}
```

##### 注意事项

⚠️ **重要提醒**:
- 必须设置回调地址，否则无法接收制作完成通知
- 请使用 `formData` 格式发送POST请求
- 在请求头设置来源域名
- 全景图片支持 JPG、JPEG、WebP 格式
- 素材名称最大支持15个字符
- 素材介绍最大支持60个字符
- media数组最大支持20组素材
- 每日最大发布作品数量与主站保持一致(60个),如需扩容请咨询客服

##### 状态码说明
| 状态码 | 说明 |
|--------|------|
| 1 | 成功 |
| 0 | 失败 |

#### 5. 发布全景素材（makeMedia()）

##### 接口地址
```
POST https://develop.9kvr.cn/api/develop/makeMedia.html?key=apiKey
```

##### 请求参数

###### 基础参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者KEY(在开发者配置页面获取) |

###### 素材信息 (media) - json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| imgs_location | string | 是 | VR素材CDN地址 |
| imgs_filename | string | 否 | VR素材名称(最大支持15个字符, 默认"来自API素材") |
| imgs_description | string | 否 | 素材介绍(最大支持60个字符, 默认"来自API素材") |

> **注意**: media 可传单个对象或数组, 数组最大支持20组素材
> **注意**: 仅生成素材, 不创建全景作品, 如需制作作品请使用 [使用API制作VR全景](https://9kvr.cn/site/user/develop/workbench.md)

##### 入参示例

###### 单个素材
```json
{
  "media": {
    "imgs_location": "https://cdn.example.com/pano.jpg",
    "imgs_filename": "全景素材1",
    "imgs_description": "我的第一个全景素材"
  }
}
```

###### 多个素材
```json
{
  "media": [
    {
      "imgs_location": "https://cdn.example.com/pano1.jpg",
      "imgs_filename": "全景素材1",
      "imgs_description": "第一个全景素材"
    },
    {
      "imgs_location": "https://cdn.example.com/pano2.jpg",
      "imgs_filename": "全景素材2",
      "imgs_description": "第二个全景素材"
    }
  ]
}
```

##### 调用示例代码

###### Node.js 示例

```javascript
const axios = require('axios');

async function makeMedia() {
  const apiKey = 'your_api_key_here';
  const url = `https://develop.9kvr.cn/api/develop/makeMedia.html?key=${apiKey}`;

  const formData = new FormData();

  // 素材信息(单个对象或数组)
  const media = [
    {
      imgs_location: 'https://cdn.example.com/pano1.jpg',
      imgs_filename: '全景素材1',
      imgs_description: '第一个全景素材'
    },
    {
      imgs_location: 'https://cdn.example.com/pano2.jpg',
      imgs_filename: '全景素材2',
      imgs_description: '第二个全景素材'
    }
  ];
  formData.append('media', JSON.stringify(media));

  try {
    const response = await axios.post(url, formData, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });
    console.log('成功:', response.data);
    return response.data;
  } catch (error) {
    console.error('失败:', error.response ? error.response.data : error.message);
    throw error;
  }
}

makeMedia();
```

###### Python 示例

```python
import requests
import json

def make_media():
    api_key = 'your_api_key_here'
    url = f'https://develop.9kvr.cn/api/develop/makeMedia.html?key={api_key}'

    # 素材信息(单个对象或数组)
    media = [
        {
            'imgs_location': 'https://cdn.example.com/pano1.jpg',
            'imgs_filename': '全景素材1',
            'imgs_description': '第一个全景素材'
        }
    ]

    data = {
        'media': json.dumps(media, ensure_ascii=False)
    }

    try:
        response = requests.post(url, data=data)
        response.raise_for_status()
        result = response.json()
        print('成功:', result)
        return result
    except requests.exceptions.RequestException as e:
        print(f'请求失败: {e}')
        return None

if __name__ == '__main__':
    make_media()
```

##### 返回示例

###### 成功响应
```json
{
  "code": 1,
  "msg": "发布素材成功",
  "info": {
    "media_id": [10001]
  }
}
```

###### 失败响应
```json
{
  "code": 0,
  "msg": "发布素材失败的原因",
  "info": {}
}
```

##### 注意事项

⚠️ **重要提醒**:
- 素材CDN地址必填, 图片支持 JPG、JPEG、WebP 格式
- 素材名称最大支持15个字符
- 素材介绍最大支持60个字符
- media数组最大支持20组素材
- 本接口仅生成素材ID, 不创建全景作品, 不触发制作
- 每日最大发布数量与主站保持一致(60个), 如需扩容请咨询客服

##### 状态码说明
| 状态码 | 说明 |
|--------|------|
| 1 | 成功 |
| 0 | 失败 |

#### 6. 通过素材库制作VR全景（makeTourByMedia()）

##### 接口地址
```
POST https://develop.9kvr.cn/api/develop/makeTourByMedia.html?key=apiKey
```

##### 请求参数

###### 基础参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者KEY(在开发者配置页面获取) |

###### 作品信息 (work) - json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| work_name | string | 是 | 作品名称 |
| work_profile | string | 否 | 作品介绍 |
| work_music | string | 否 | 作品全局背景音乐(仅限mp3文件) |

###### 素材信息 (media) - json 格式
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| imgs_workid | string | 是 | 素材ID(来自 [发布全景素材](https://9kvr.cn/site/user/develop/workbench.md) 或 [查询素材列表](https://9kvr.cn/site/user/develop/workbench.md)) |

> **注意**: 
> - media 可传单个对象或数组, 数组最大支持20组素材
> - 素材必须是当前开发者账号下已发布的素材, 且素材制作已完成
> - 该接口直接使用素材库已有的成品素材制作作品, 无需再传CDN地址

##### 入参示例

```json
{
  "work": {
    "work_name": "示例作品",
    "work_profile": "作品介绍"
  },
  "key": "开发者KEY(在开发者配置页面获取)",
  "media": [
    {
      "imgs_workid": "YOUR_MEDIA_ID"
    },
    {
      "imgs_workid": "YOUR_MEDIA_ID"
    }
  ]
}
```

##### 调用示例代码

###### Node.js 示例

```javascript
const axios = require('axios');

async function makeTourByMedia() {
  const apiKey = 'your_api_key_here';
  const url = `https://develop.9kvr.cn/api/develop/makeTourByMedia.html?key=${apiKey}`;

  const formData = new FormData();
  formData.append('work', JSON.stringify({
    work_name: '我的作品',
    work_profile: '作品介绍'
  }));
  // 素材ID列表(来自素材列表接口, 值为 imgs_workid)
  formData.append('media', JSON.stringify([
    { imgs_workid: 'YOUR_MEDIA_ID' },
    { imgs_workid: 'YOUR_MEDIA_ID' }
  ]));

  try {
    const response = await axios.post(url, formData, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });
    console.log('成功:', response.data);
    return response.data;
  } catch (error) {
    console.error('失败:', error.response ? error.response.data : error.message);
    throw error;
  }
}

makeTourByMedia();
```

##### 返回示例

###### 成功响应
```json
{
  "code": 1,
  "msg": "制作成功",
  "info": "作品viewid"
}
```

###### 失败响应
```json
{
  "code": 0,
  "msg": "发布失败的原因",
  "info": []
}
```

##### 注意事项

⚠️ **重要提醒**:
- 素材库的素材必须属于当前开发者账号
- 想查看开发者账号下已有的素材, 可先调用 [查询素材列表](https://9kvr.cn/site/user/develop/workbench.md)
- 具体制作进度, 请参考 [获取全景作品列表](https://9kvr.cn/site/user/develop/workbench.md) 中的作品状态

#### 7. 查询素材列表（getMediaList()）

##### 接口地址
```
POST https://develop.9kvr.cn/api/develop/getMediaList.html?key=apiKey
```

##### 请求参数

###### 基础参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| key | string | 是 | 开发者KEY(在开发者配置页面获取) |
| page | number | 否 | 页码, 默认1 |
| limit | number | 否 | 每页数量, 默认12 |
| status | number | 否 | 素材状态, 0全部(默认), 1制作中, 2制作完成 |
| keyword | string | 否 | 素材名称关键词搜索 |

##### 入参示例

```
POST ?key=apiKey&page=1&limit=12&status=0
```

##### 调用示例代码

###### Node.js 示例

```javascript
const axios = require('axios');

async function getMediaList() {
  const apiKey = 'your_api_key_here';
  const url = `https://develop.9kvr.cn/api/develop/getMediaList.html?key=${apiKey}`;

  const formData = new FormData();
  formData.append('page', '1');
  formData.append('limit', '12');
  formData.append('status', '0');

  try {
    const response = await axios.post(url, formData, {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    });
    console.log('成功:', response.data);
    return response.data;
  } catch (error) {
    console.error('失败:', error.response ? error.response.data : error.message);
    throw error;
  }
}

getMediaList();
```

##### 返回示例

###### 成功响应
```json
{
  "code": 1,
  "msg": "获取成功",
  "info": {
    "list": [
      {
        "id": "YOUR_MEDIA_ID",
        "imgs_workid": "YOUR_MEDIA_ID",
        "imgs_filename": "客厅全景",
        "imgs_description": "客厅裸眼全景图",
        "imgs_location": "https://cdn.example.com/panorama.jpg",
        "imgs_thumb": "https://cdn.example.com/example-pano.jpg",
        "imgs_size": 2.5,
        "imgs_width": 8400,
        "imgs_height": 4100,
        "imgs_status": 2,
        "imgs_type": 1,
        "imgs_version": 1,
        "imgs_slice_type": 1,
        "imgs_createtime": "2026-08-08 10:00:00"
      }
    ],
    "page": 1,
    "limit": 12,
    "total": 30,
    "total_page": 3
  }
}
```

###### 失败响应
```json
{
  "code": 0,
  "msg": "权限验证失败,请检查key是否正确",
  "info": []
}
```

##### 返回字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 素材ID(与 imgs_workid 相同, 制作作品时使用) |
| imgs_workid | string | 素材对外ID(无规律字符串) |
| imgs_filename | string | 素材名称 |
| imgs_description | string | 素材介绍 |
| imgs_thumb | string | 素材缩略图(可直接访问的完整URL) |
| imgs_size | number | 素材大小(M) |
| imgs_width | number | 素材宽度 |
| imgs_height | number | 素材高度 |
| imgs_status | number | 素材状态, 1制作中, 2制作完成 |
| imgs_type | number | 素材类型, 1全景图片 |
| imgs_version | number | 素材版本 |
| imgs_slice_type | number | 素材切片类型 |
| imgs_createtime | string | 素材创建时间 |

##### 注意事项

⚠️ **重要提醒**:
- 素材列表查询的素材必须是当前开发者发布的素材
- `status=2` 表示素材已完成, 用于 [通过素材库制作VR全景](https://9kvr.cn/site/user/develop/workbench.md)

#### 8. 使用小程序跳转打开全景（navigateToMiniProgram()）

##### 平台介绍
芊云全景作为一个开放性较强的全景开发平台，我们也赋予了平台一直开发者支持。下面简单介绍一下其他小程序打开芊云全景小程序的方案。

##### 兼容性说明

###### 基础库支持
- **基础库版本**: 1.3.0 开始支持，低版本需做兼容处理
- **Promise风格**: 支持
- **页面权限**: 小程序不能在插件页面中调用该接口，插件也不能在小程序页面中调用该接口
- **小程序插件**: 支持，需要小程序基础库版本不低于 2.18.1
- **微信 Windows 版**: 支持
- **微信 Mac 版**: 支持

##### 小程序信息

| 信息类型 | 值 | 说明 |
|----------|-----|------|
| AppID | wx9f77d65eb4eff65b | 芊云全景小程序AppID |
| 环境版本 | release | 正式环境 |

##### 页面路径

| 页面类型 | 路径 | 说明 |
|----------|------|------|
| 小程序首页 | pages/index/index | 小程序主页面 |
| 全景加载页面 | pages/tour/wait?id=全景ID | 全景加载等待页面 |
| 全景播放器详情页 | pages/tour/index?id=全景ID | 全景播放器页面 |
| 个人主页 | pages/index/home?id=自己UID | 用户个人主页 |

##### 跳转方式

###### 基础跳转
```javascript
wx.navigateToMiniProgram({
  appId: 'wx9f77d65eb4eff65b',
  path: 'pages/index/index',
  extraData: {},
  envVersion: 'release',
  success(res) {
    console.log('打开成功');
  },
  fail(err) {
    console.error('打开失败', err);
  }
});
```

###### 带参数跳转（当前暂未启用）
⚠️ **注意**: 当前暂未启用，也许在后续某个版本启用

直接打开播放器详情页面，需要携带芊云全景提供的 secretKey 参数，否则即使打开，也会强制跳转到加载页面。

```javascript
wx.navigateToMiniProgram({
  appId: 'wx9f77d65eb4eff65b',
  path: 'pages/tour/wait?id=全景ID',
  extraData: {
    secretKey: '开发者服务页面获取',
    appName: '您自己小程序名称'
  },
  envVersion: 'release',
  success(res) {
    console.log('打开成功');
  },
  fail(err) {
    console.error('打开失败', err);
  }
});
```

##### 参数说明

###### 跳转参数 (extraData)
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| secretKey | string | 否 | 开发者服务页面获取的密钥（当前暂未启用） |
| appName | string | 否 | 您自己小程序名称，携带后若返回小程序则显示来源小程序名称，否则只显示"返回来源小程序" |

###### 返回参数 (extraData)
| 参数名 | 类型 | 说明 |
|--------|------|------|
| comeBack | string | 固定值 'vr29' |
| id | string | 打开全景的ID |

##### 返回来源小程序

###### 芊云全景逻辑处理
若是来自其他小程序，等待页面的"返回"或者"查看更多全景"将变为"返回来源小程序"，返回来源小程序后携带参数如下：

```javascript
wx.navigateBackMiniProgram({
  extraData: {
    comeBack: 'vr29',
    id: '打开全景的ID'
  },
  success(res) {
    console.log('返回成功');
  },
  fail(err) {
    console.error('返回失败', err);
  }
});
```

##### 开发者集成

###### 获取来源参数
开发者可以自行在自己小程序的生命周期内获取来源参数！

```javascript
// 在小程序页面中获取来源参数
onLoad(options) {
  if (options.comeBack === 'vr29') {
    console.log('来自芊云全景小程序');
    console.log('全景ID:', options.id);
  }
}
```

##### 注意事项
1. 需要在小程序后台配置跳转关系
2. 确保secretKey有效且具有跳转权限（当前暂未启用）
3. 建议在跳转前检查用户登录状态
4. 支持传递自定义参数到全景页面
5. 注意基础库版本兼容性
6. 开发者可以自行处理返回参数

##### 相关文档
- [芊云全景小程序跳转开发文档](https://mgihrroao7.feishu.cn/docx/OQNgdUQnzoMjs1xPvbjcZ9ynnih?from=from_copylink)

#### 9. 芊云全景打开半屏小程序VR（openEmbeddedMiniProgram()）

##### 功能说明
在芊云全景H5页面中，可以打开半屏小程序来展示VR内容，提供更好的用户体验。

##### 重要提醒
⚠️ **注意**: 自己小程序打开芊云全景为半屏小程序目前可以免费使用。
但是若直接打开全景播放器(pages/tour/index)有广告，跳转其他页面无广告。
开通 VIP 可去除所有广告，其他流程均不受影响。

##### 配置步骤

###### 1. 添加小程序权限
在小程序后台申请半屏能力，添加芊云全景小程序。

###### 2. 搜索并添加小程序
- **搜索方式**: 搜索"芊云全景"
- **AppID**: 如果搜索不到，请填写 `wx9f77d65eb4eff65b` 进行搜索
- **审核**: 添加后等待管理员审核，也可以联系客服进行审核

##### 小程序页面路径

| 页面类型 | 路径 | 说明 |
|----------|------|------|
| 小程序首页 | pages/index/index | 小程序主页面 |
| 全景加载页面 | pages/tour/wait?id=全景ID | 全景加载等待页面 |
| 全景播放器详情页 | pages/tour/index?id=全景ID | 全景播放器页面 |
| 个人主页 | pages/index/home?id=自己UID | 用户个人主页 |

##### 接入代码示例

###### 打开半屏小程序
```javascript
wx.openEmbeddedMiniProgram({
  appId: 'wx9f77d65eb4eff65b',
  path: 'pages/tour/index?id=全景ID',
  extraData: {
    secretKey: '开发者服务页面获取',
    appName: '您自己小程序名称'
  },
  envVersion: 'release',
  success(res) {
    console.log('打开半屏小程序成功');
  },
  fail(err) {
    console.error('打开失败', err);
  }
});
```

###### 返回来源小程序
```javascript
wx.navigateBackMiniProgram({
  extraData: {
    comeBack: 'vr29',
    id: '打开全景的ID'
  },
  success(res) {
    console.log('返回成功');
  },
  fail(err) {
    console.error('返回失败', err);
  }
});
```

##### 参数说明

###### 打开参数 (extraData)
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| secretKey | string | 是 | 开发者服务页面获取的密钥 |
| appName | string | 否 | 您自己小程序名称，携带后若返回小程序则显示来源小程序名称，否则只显示"返回来源小程序" |

###### 返回参数 (extraData)
| 参数名 | 类型 | 说明 |
|--------|------|------|
| comeBack | string | 固定值 'vr29' |
| id | string | 打开全景的ID |

##### 获取参数方法

###### 如何获取全景ID
打开任意一个作品获取 `id=xxx`，其中的 `xxx` 就是全景ID

###### 如何获取 secretKey
在芊云开发者服务平台获取开发者密钥

##### 逻辑处理说明

###### 芊云全景逻辑处理
若是来自其他小程序，等待页面的"返回"或者"查看更多全景"将变为"返回来源小程序"，返回来源小程序后携带参数如上所示。

##### 注意事项
1. 半屏小程序需要特殊申请和审核
2. 建议在小程序中提供返回按钮
3. 注意半屏小程序的尺寸限制
4. 确保H5页面和小程序的数据同步
5. 直接打开播放器详情页面，需要携带芊云全景提供的 secretKey 参数，否则即使打开，也会强制跳转到加载页面

##### 相关文档
- [微信半屏小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/openEmbeddedMiniProgram.html)
- [芊云全景半屏小程序开发文档](https://mgihrroao7.feishu.cn/docx/HjCAd31HdowFvVxnpoXcELVmnwd?from=from_copylink)

#### 10. 使用iframe打开全景（embedIframe()）

##### 功能说明
使用iframe标签在网页中嵌入芊云全景播放器，实现无缝的全景展示体验。

##### 基础用法

###### 简单iframe嵌入
```html
<iframe 
  src="https://develop.9kvr.cn/tour/index?id=全景ID&key=apiKey" 
  width="100%" 
  height="600px" 
  frameborder="0">
</iframe>
```

###### 全屏无边框iframe
```html
<iframe 
  src="https://develop.9kvr.cn/tour/index?id=全景ID&key=apiKey" 
  style="width: 100vw; height: 100vh; border: none; position: fixed; top: 0; left: 0; z-index: 9999;">
</iframe>
```

##### 参数说明

###### iframe属性
| 属性名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| src | string | 是 | 全景播放器URL |
| width | string | 否 | 宽度，建议使用100%或100vw |
| height | string | 否 | 高度，建议使用100vh实现全屏 |
| frameborder | string | 否 | 边框，建议设置为"0"或"no" |
| scrolling | string | 否 | 滚动条，建议设置为"no" |

###### URL参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | string | 是 | 全景作品ID |
| key | string | 是 | 开发者API密钥 |

##### 样式配置

###### 基础样式
```css
.vr-iframe {
  width: 100%;
  height: 600px;
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
```

###### 全屏样式
```css
.vr-iframe-fullscreen {
  width: 100vw;
  height: 100vh;
  border: none;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  background: #000;
}
```

###### 响应式样式
```css
.vr-iframe-responsive {
  width: 100%;
  height: 0;
  padding-bottom: 56.25%; /* 16:9 宽高比 */
  position: relative;
  border: none;
}

.vr-iframe-responsive iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
```

##### 完整示例

###### HTML结构
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>全景展示</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        
        .vr-iframe {
            width: 100%;
            height: 600px;
            border: none;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        }
        
        .fullscreen-btn {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        
        .fullscreen-iframe {
            width: 100vw;
            height: 100vh;
            border: none;
            position: fixed;
            top: 0;
            left: 0;
            z-index: 9999;
            background: #000;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>全景展示</h1>
        <iframe 
            id="vrIframe"
            class="vr-iframe"
            src="https://develop.9kvr.cn/tour/index?id=全景ID&key=apiKey">
        </iframe>
    </div>
    
    <button class="fullscreen-btn" onclick="toggleFullscreen()">全屏显示</button>
    
    <script>
        function toggleFullscreen() {
            const iframe = document.getElementById('vrIframe');
            if (iframe.classList.contains('fullscreen-iframe')) {
                iframe.classList.remove('fullscreen-iframe');
                iframe.classList.add('vr-iframe');
            } else {
                iframe.classList.remove('vr-iframe');
                iframe.classList.add('fullscreen-iframe');
            }
        }
        
        // 监听ESC键退出全屏
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') {
                const iframe = document.getElementById('vrIframe');
                if (iframe.classList.contains('fullscreen-iframe')) {
                    toggleFullscreen();
                }
            }
        });
    </script>
</body>
</html>
```

##### 移动端适配

###### 移动端样式
```css
@media screen and (max-width: 768px) {
    .vr-iframe {
        height: 400px;
    }
    
    .fullscreen-iframe {
        width: 100vw;
        height: 100vh;
    }
}
```

##### 注意事项

⚠️ **重要提醒**:
- 确保全景ID和API密钥正确
- 建议使用HTTPS协议访问iframe内容
- 注意跨域问题，确保域名已授权
- 全屏模式下注意z-index层级管理
- 移动端建议使用响应式设计
- 注意iframe加载性能优化

##### 常见问题

###### Q: iframe无法加载怎么办？
A: 检查URL参数是否正确，确保网络连接正常，查看浏览器控制台错误信息。

###### Q: 如何实现自适应高度？
A: 可以使用JavaScript监听iframe内容高度变化，动态调整iframe高度。

###### Q: 如何去除iframe边框？
A: 设置`frameborder="0"`和CSS中的`border: none`。

###### Q: 全屏模式下如何退出？
A: 可以监听ESC键或添加退出按钮，切换CSS类名即可。

##### 相关文档
- [芊云全景播放器文档](https://develop.9kvr.cn)
- [HTML iframe 标签文档](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe)

<!-- public-api:end -->

## 4. 使用说明

1. 登录后进入开发者工作台
2. 在「开发初始配置」中完成基本信息配置
3. 切换到接口文档、小程序工作台、离线下载等区块
4. 如需自定义 CDN 素材，先开通开发版及以上会员

## 5. 规则与限制

- 自定义 CDN 素材需开发版及以上会员
- API 读写能力（获取作品信息、管理全景作品、提交制作、接收结果通知）需企业版及以上会员，开发版不含
- 未完成开发者基本信息配置时，切换区块会提示先完成配置
- 未登录时不进行额外初始化

## 6. 状态与异常

- 加载中：检查登录态与会员等级
- 正常：可切换各区块
- 受限：自定义 CDN 区块未达要求时展示开通提示
- 未完成基本信息配置时切换区块提示先配置
- 未开通开发版会员时自定义 CDN 区块不可用

## 7. 相关文档

- [开发者平台首页](https://9kvr.cn/site/user/develop/welcome.md)
- [插件市场](https://9kvr.cn/site/user/develop/plugins.md)
- [VIP 会员服务](https://9kvr.cn/site/user/vip/v2.md)
- 上级目录索引：[/user](https://9kvr.cn/site/user/llms.txt)

## 8. 文档信息

- 事实来源：芊云全景官网页面核验
- 文档构建时间：2026-10-20 19:04:23
- 数据获取/快照时间：不适用（静态产品知识）
- 可信状态：已核验
