1. 新增校园巴士、智能体、我的活动等多个业务页面 2. 新增svg-icon、uploadFile通用组件 3. 新增数十个图标与背景图片资源 4. 优化活动创建、详情页面的上传与样式逻辑 5. 更新全局配置与自定义tabbarmaster
@ -0,0 +1,8 @@ |
|||||||
|
图片目录地址 |
||||||
|
git http://39.106.86.127:3000/hb_rd/front_dist.git |
||||||
|
branch proj_icampus_dev |
||||||
|
|
||||||
|
powershell 软链形式 |
||||||
|
``` |
||||||
|
New-Item -ItemType Junction -Path "src/images" -Target C:\Users\kola\project\school-system\web_dist\images |
||||||
|
``` |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
# Change to the src/images directory, or exit if the directory doesn't exist |
||||||
|
Set-Location -Path C:\Users\kola\project\school-system\web_dist\images -ErrorAction Stop |
||||||
|
|
||||||
|
# 1. 添加当前目录所有文件(包含.gitignore忽略的文件也一并加入,等价svn --no-ignore --force) |
||||||
|
git add --force . |
||||||
|
|
||||||
|
# 2. 提交,备注update |
||||||
|
git commit -m "update" |
||||||
|
|
||||||
|
git push origin proj_icampus_dev |
||||||
|
|
||||||
|
ssh hb127 "cd /data1/wwwroot/default/highedu/web_dist_server/dist && git pull && exit" |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
# SVG Icon 组件 |
||||||
|
|
||||||
|
微信小程序 SVG 组件,支持对 SVG 重新着色。 |
||||||
|
|
||||||
|
## 组件路径 |
||||||
|
|
||||||
|
`/components/svg-icon/index` |
||||||
|
|
||||||
|
## 安装 |
||||||
|
|
||||||
|
在页面或全局 `json` 配置中引入组件: |
||||||
|
|
||||||
|
```json |
||||||
|
{ |
||||||
|
"usingComponents": { |
||||||
|
"svg-icon": "/components/svg-icon/index" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## 基础用法 |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- 直接使用 SVG 文件 --> |
||||||
|
<svg-icon src="/images/icon.svg" /> |
||||||
|
|
||||||
|
<!-- 指定缩放模式 --> |
||||||
|
<svg-icon src="/images/icon.svg" mode="widthFix" /> |
||||||
|
``` |
||||||
|
|
||||||
|
## 单颜色重新着色 |
||||||
|
|
||||||
|
传入 `color` 时,会对 SVG 中所有声明 `fill/stroke` 的元素统一着色: |
||||||
|
|
||||||
|
```xml |
||||||
|
<svg-icon src="/images/icon.svg" color="#ff0000" /> |
||||||
|
``` |
||||||
|
|
||||||
|
## 多颜色重新着色 |
||||||
|
|
||||||
|
### 数组形式(按顺序替换) |
||||||
|
|
||||||
|
以数组形式传入 `colors` 时,依照数组中的颜色顺序,对 SVG 中所有声明 `fill/stroke` 的元素按顺序重新着色: |
||||||
|
|
||||||
|
```ts |
||||||
|
// page.ts |
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
colorsArray: ['#ff0000', '#00ff00', '#0000ff'], |
||||||
|
}, |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- page.wxml --> |
||||||
|
<svg-icon src="/images/icon.svg" colors="{{colorsArray}}" /> |
||||||
|
``` |
||||||
|
|
||||||
|
### 对象形式(按键值关系替换) |
||||||
|
|
||||||
|
以对象形式传入 `colors` 时,依照对象中的键值关系,对 SVG 中所有声明 `fill/stroke` 的元素按对应关系重新着色: |
||||||
|
|
||||||
|
```ts |
||||||
|
// page.ts |
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
colorsObject: { |
||||||
|
black: '#ff0000', |
||||||
|
'#fff': '#00ff00', |
||||||
|
'#808080': '#cdcdcd', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- page.wxml --> |
||||||
|
<svg-icon src="/images/icon.svg" colors="{{colorsObject}}" /> |
||||||
|
``` |
||||||
|
|
||||||
|
## 组合重新着色 |
||||||
|
|
||||||
|
同时传入 `color` 和 `colors` 组合搭配,既能为指定元素重新着色,也能为其余未指定元素统一着色: |
||||||
|
|
||||||
|
```ts |
||||||
|
// page.ts |
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
colorsArray: ['#ff0000', '#00ff00'], |
||||||
|
}, |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- page.wxml --> |
||||||
|
<svg-icon src="/images/icon.svg" color="#0000ff" colors="{{colorsArray}}" /> |
||||||
|
``` |
||||||
|
|
||||||
|
## 网络资源 |
||||||
|
|
||||||
|
支持传入网络 SVG 资源地址: |
||||||
|
|
||||||
|
```xml |
||||||
|
<svg-icon src="https://example.com/icon.svg" color="#ff0000" /> |
||||||
|
``` |
||||||
|
|
||||||
|
**注意**:当 src 传入网络资源并重新着色时,请将网络资源的域名配置于小程序的 `downloadFile` 合法域名中。 |
||||||
|
|
||||||
|
## API |
||||||
|
|
||||||
|
### Properties |
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 | 必填 | |
||||||
|
| --- | --- | --- | --- | --- | |
||||||
|
| `src` | SVG 资源地址(支持本地路径、临时路径、网络资源) | `string` | `''` | 是 | |
||||||
|
| `color` | SVG 单一颜色(对所有 fill/stroke 元素统一着色) | `string` | `''` | 否 | |
||||||
|
| `colors` | SVG 多颜色配置(支持数组或对象) | `array \| object` | `null` | 否 | |
||||||
|
| `mode` | SVG 裁剪、缩放模式(与 `image` 标签相同) | `string` | `''` | 否 | |
||||||
|
|
||||||
|
### Events |
||||||
|
|
||||||
|
| 事件名 | 说明 | 回调参数 | |
||||||
|
| --- | --- | --- | |
||||||
|
| `bind:error` | 当错误发生时触发 | `event.detail = Error \| { errMsg }` | |
||||||
|
| `bind:load` | 当图片载入完毕时触发 | `event.detail = { height, width }` | |
||||||
|
|
||||||
|
### External Classes |
||||||
|
|
||||||
|
| 类名 | 说明 | |
||||||
|
| --- | --- | |
||||||
|
| `image-class` | `image` 节点样式类 | |
||||||
|
|
||||||
|
## mode 可选值 |
||||||
|
|
||||||
|
与微信小程序 `image` 组件的 `mode` 属性相同: |
||||||
|
|
||||||
|
| 值 | 说明 | |
||||||
|
| --- | --- | |
||||||
|
| `scaleToFill` | 不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 | |
||||||
|
| `aspectFit` | 保持纵横比缩放图片,使图片的长边能完全显示出来 | |
||||||
|
| `aspectFill` | 保持纵横比缩放图片,只保证图片的短边能完全显示出来 | |
||||||
|
| `widthFix` | 宽度不变,高度自动变化,保持原图宽高比不变 | |
||||||
|
| `heightFix` | 高度不变,宽度自动变化,保持原图宽高比不变 | |
||||||
|
| `top` | 不缩放图片,只显示图片的顶部区域 | |
||||||
|
| `bottom` | 不缩放图片,只显示图片的底部区域 | |
||||||
|
| `center` | 不缩放图片,只显示图片的中间区域 | |
||||||
|
| `left` | 不缩放图片,只显示图片的左边区域 | |
||||||
|
| `right` | 不缩放图片,只显示图片的右边区域 | |
||||||
|
| `top left` | 不缩放图片,只显示图片的左上边区域 | |
||||||
|
| `top right` | 不缩放图片,只显示图片的右上边区域 | |
||||||
|
| `bottom left` | 不缩放图片,只显示图片的左下边区域 | |
||||||
|
| `bottom right` | 不缩放图片,只显示图片的右下边区域 | |
||||||
|
|
||||||
|
## 实现原理 |
||||||
|
|
||||||
|
1. 读取 SVG 文件内容(本地文件或网络文件) |
||||||
|
2. 通过正则替换 SVG 中的 `fill/stroke` 属性值来实现改色 |
||||||
|
3. 将修改后的 SVG 内容转为 base64 格式,作为 `image` 的 `src` |
||||||
|
|
||||||
|
## 注意事项 |
||||||
|
|
||||||
|
- SVG 文件必须包含 `fill` 或 `stroke` 属性才能被改色 |
||||||
|
- 网络资源需要配置 `downloadFile` 合法域名 |
||||||
|
- 组件会缓存下载的网络资源,避免重复下载 |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
/** |
||||||
|
* Base64 编码工具 |
||||||
|
* 用于将 SVG 内容转为 base64 格式 |
||||||
|
*/ |
||||||
|
|
||||||
|
// Base64 字符映射表
|
||||||
|
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' |
||||||
|
|
||||||
|
/** |
||||||
|
* 将字符串编码为 Base64 |
||||||
|
* @param str 要编码的字符串 |
||||||
|
* @returns Base64 编码后的字符串 |
||||||
|
*/ |
||||||
|
export function encode(str: string): string { |
||||||
|
let result = '' |
||||||
|
const len = str.length |
||||||
|
const remainder = len % 3 |
||||||
|
|
||||||
|
for (let i = 0; i < len - remainder; i += 3) { |
||||||
|
const n = (str.charCodeAt(i) << 16) | (str.charCodeAt(i + 1) << 8) | str.charCodeAt(i + 2) |
||||||
|
result += CHARS[(n >> 18) & 63] + CHARS[(n >> 12) & 63] + CHARS[(n >> 6) & 63] + CHARS[n & 63] |
||||||
|
} |
||||||
|
|
||||||
|
// 处理剩余字符
|
||||||
|
if (remainder === 1) { |
||||||
|
const n = str.charCodeAt(len - 1) |
||||||
|
result += `${CHARS[(n >> 2) & 63] + CHARS[(n << 4) & 63] }==` |
||||||
|
} else if (remainder === 2) { |
||||||
|
const n = (str.charCodeAt(len - 2) << 8) | str.charCodeAt(len - 1) |
||||||
|
result += `${CHARS[(n >> 10) & 63] + CHARS[(n >> 4) & 63] + CHARS[(n << 2) & 63] }=` |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"component": true, |
||||||
|
"usingComponents": {} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
/** |
||||||
|
* SVG Icon 组件样式 |
||||||
|
*/ |
||||||
|
|
||||||
|
.svg-icon { |
||||||
|
display: block; |
||||||
|
} |
||||||
@ -0,0 +1,156 @@ |
|||||||
|
/** |
||||||
|
* SVG Icon 组件 |
||||||
|
* 支持对 SVG 重新着色 |
||||||
|
*/ |
||||||
|
|
||||||
|
import { encode } from './base64' |
||||||
|
|
||||||
|
const fs = wx.getFileSystemManager() |
||||||
|
|
||||||
|
// 临时文件缓存(网络资源下载后缓存)
|
||||||
|
const tempFileMap = new Map<string, string>() |
||||||
|
|
||||||
|
/** |
||||||
|
* 同步下载网络文件 |
||||||
|
* @param url 网络资源地址 |
||||||
|
* @returns 下载结果 |
||||||
|
*/ |
||||||
|
function downloadFileSync(url: string): Promise<WechatMiniprogram.DownloadFileSuccessCallbackResult> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
wx.downloadFile({ |
||||||
|
url, |
||||||
|
success: resolve, |
||||||
|
fail: reject, |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
Component({ |
||||||
|
options: { |
||||||
|
styleIsolation: 'shared', |
||||||
|
}, |
||||||
|
|
||||||
|
externalClasses: ['image-class'], |
||||||
|
|
||||||
|
properties: { |
||||||
|
/** SVG 资源地址(支持本地路径、临时路径、网络资源) */ |
||||||
|
src: { |
||||||
|
type: String, |
||||||
|
value: '', |
||||||
|
}, |
||||||
|
/** SVG 单一颜色(对所有 fill/stroke 元素统一着色) */ |
||||||
|
color: { |
||||||
|
type: String, |
||||||
|
value: '', |
||||||
|
}, |
||||||
|
/** SVG 多颜色配置(支持数组或对象) */ |
||||||
|
colors: { |
||||||
|
type: null, |
||||||
|
value: null, |
||||||
|
}, |
||||||
|
/** SVG 裁剪、缩放模式(与 image 标签相同) */ |
||||||
|
mode: { |
||||||
|
type: String, |
||||||
|
value: '', |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
observers: { |
||||||
|
'src, color, colors': async function (src: string, color: string, colors: unknown) { |
||||||
|
try { |
||||||
|
// 如果需要改色
|
||||||
|
if (color || (colors && ((Array.isArray(colors) && colors.length > 0) || (typeof colors === 'object' && Object.keys(colors).length > 0)))) { |
||||||
|
let svgData: string |
||||||
|
|
||||||
|
// 判断是否为网络资源(排除开发工具临时路径 http://tmp/)
|
||||||
|
if (/^https?:\/\//.test(src) && !/^http:\/\/tmp\//.test(src)) { |
||||||
|
// 网络资源需要先下载
|
||||||
|
let tempFilePath = tempFileMap.get(src) |
||||||
|
try { |
||||||
|
if (!tempFilePath) throw new Error('未缓存') |
||||||
|
// 检查临时文件是否存在
|
||||||
|
fs.accessSync(tempFilePath) |
||||||
|
} catch { |
||||||
|
// 下载文件
|
||||||
|
const downloadResult = await downloadFileSync(src) |
||||||
|
tempFilePath = downloadResult.tempFilePath |
||||||
|
// 缓存临时文件路径
|
||||||
|
tempFileMap.set(src, tempFilePath) |
||||||
|
} |
||||||
|
// 读取文件内容
|
||||||
|
svgData = fs.readFileSync(tempFilePath, 'utf8') as string |
||||||
|
} else { |
||||||
|
// 本地资源直接读取
|
||||||
|
svgData = fs.readFileSync(src, 'utf8') as string |
||||||
|
} |
||||||
|
|
||||||
|
// 处理颜色配置
|
||||||
|
const colorsConfig = colors || {} |
||||||
|
|
||||||
|
// 替换 SVG 中的 fill/stroke 属性
|
||||||
|
if (/(?:fill|stroke)=".*?"/.test(svgData)) { |
||||||
|
let colorIndex = 0 |
||||||
|
svgData = svgData.replace(/(?:fill|stroke)=".*?"/g, (matched) => { |
||||||
|
// 获取原本颜色值
|
||||||
|
const originalColor = matched.slice(matched.indexOf('"') + 1, -1) |
||||||
|
|
||||||
|
// 计算替换颜色
|
||||||
|
let replaceColor: string |
||||||
|
if (Array.isArray(colorsConfig)) { |
||||||
|
// 数组形式:按顺序替换
|
||||||
|
replaceColor = colorsConfig[colorIndex++] || color || originalColor |
||||||
|
} else { |
||||||
|
// 对象形式:按键值关系替换
|
||||||
|
replaceColor = colorsConfig[originalColor] || colorsConfig[colorIndex++] || color || originalColor |
||||||
|
} |
||||||
|
|
||||||
|
// 返回替换后的属性
|
||||||
|
if (/fill/.test(matched)) return `fill="${replaceColor}"` |
||||||
|
if (/stroke/.test(matched)) return `stroke="${replaceColor}"` |
||||||
|
return `fill="${replaceColor}"` |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 设置默认底色(SVG 根元素)
|
||||||
|
const defaultColor = (typeof colorsConfig === 'object' && !Array.isArray(colorsConfig) && (colorsConfig['#000'] || colorsConfig['#000000'] || colorsConfig.black)) || color |
||||||
|
if (defaultColor && !/fill=".*?"/.test(svgData.slice(0, svgData.indexOf('>')))) { |
||||||
|
svgData = svgData.replace(/<svg /, `<svg fill="${defaultColor}" `) |
||||||
|
} |
||||||
|
|
||||||
|
// 转为 base64 格式
|
||||||
|
this.setData({ |
||||||
|
base64: `data:image/svg+xml;base64,${encode(svgData)}`, |
||||||
|
}) |
||||||
|
} else { |
||||||
|
// 不需要改色,直接使用原路径
|
||||||
|
this.setData({ base64: src }) |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
console.error('SVG 加载失败:', err) |
||||||
|
this.triggerEvent('error', err) |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
data: { |
||||||
|
base64: '', |
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
/** |
||||||
|
* 图片加载错误 |
||||||
|
*/ |
||||||
|
onImageError(e: WechatMiniprogram.ImageError) { |
||||||
|
this.triggerEvent('error', e.detail) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 图片加载完成 |
||||||
|
*/ |
||||||
|
onImageLoad(e: WechatMiniprogram.ImageLoad) { |
||||||
|
this.triggerEvent('load', e.detail) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<!-- |
||||||
|
SVG Icon 组件 |
||||||
|
支持对 SVG 重新着色 |
||||||
|
--> |
||||||
|
<image |
||||||
|
class="svg-icon image-class" |
||||||
|
src="{{base64}}" |
||||||
|
mode="{{mode}}" |
||||||
|
binderror="onImageError" |
||||||
|
bindload="onImageLoad" |
||||||
|
/> |
||||||
@ -0,0 +1,158 @@ |
|||||||
|
# Upload 上传组件 API 文档 |
||||||
|
|
||||||
|
## 组件路径 |
||||||
|
|
||||||
|
`@/components/uploadFile/index` |
||||||
|
|
||||||
|
## 说明 |
||||||
|
|
||||||
|
上传接口地址固定为 `app.globalData.upFileUrl`,组件内部自动附加 `loginState`,无需外部传入。 |
||||||
|
|
||||||
|
## Properties 入参 |
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 | 必填 | |
||||||
|
| --- | --- | --- | --- | --- | |
||||||
|
| `maxCount` | 最大上传文件数 | `Number` | `9` | 否 | |
||||||
|
| `maxSize` | 单文件最大限制(byte) | `Number` | `10485760`(10MB) | 否 | |
||||||
|
| `accept` | 允许的文件类型数组,可选值:`'image'` \| `'video'` \| `'file'` | `Array` | `['image']` | 否 | |
||||||
|
| `extensions` | 自定义文件后缀(仅 accept 含 `'file'` 时生效),如 `['.pdf', '.doc']` | `Array` | `[]` | 否 | |
||||||
|
| `readonly` | 只读模式(不显示上传和删除按钮) | `Boolean` | `false` | 否 | |
||||||
|
| `useSlot` | 是否使用自定义上传区域插槽 | `Boolean` | `false` | 否 | |
||||||
|
| `fileList` | 已有文件列表(用于回显) | `Array` | `[]` | 否 | |
||||||
|
|
||||||
|
### fileList 数据结构 |
||||||
|
|
||||||
|
```ts |
||||||
|
{ |
||||||
|
uid: string // 文件唯一标识 |
||||||
|
url: string // 文件路径 |
||||||
|
type: string // 文件类型: 'image' | 'video' | 'file' |
||||||
|
name: string // 文件名 |
||||||
|
size: number // 文件大小(byte) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Events 事件 |
||||||
|
|
||||||
|
| 事件名 | 说明 | 回调参数 | |
||||||
|
| --- | --- | --- | |
||||||
|
| `bind:select` | 选中本地文件后触发 | `{ files: UploadFile[] }` | |
||||||
|
| `bind:success` | 单文件上传完成 | `{ file: UploadFile, response: any }` | |
||||||
|
| `bind:error` | 上传失败 | `{ file: UploadFile, error: Error }` | |
||||||
|
| `bind:remove` | 删除文件 | `{ file: UploadFile, fileList: UploadFile[] }` | |
||||||
|
|
||||||
|
### 事件返回的 UploadFile 结构 |
||||||
|
|
||||||
|
```ts |
||||||
|
{ |
||||||
|
uid: string // 文件唯一标识 |
||||||
|
url: string // 文件路径(上传成功后为远程 url) |
||||||
|
type: 'image' | 'video' | 'file' // 文件类型 |
||||||
|
name: string // 文件名 |
||||||
|
size: number // 文件大小(byte) |
||||||
|
status: 'pending' | 'uploading' | 'success' | 'error' // 上传状态 |
||||||
|
progress: number // 上传进度 0-100 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Slots 插槽 |
||||||
|
|
||||||
|
| 插槽名 | 说明 | |
||||||
|
| --- | --- | |
||||||
|
| `upload-area` | 自定义上传区域布局,需同时设置 `useSlot="{{true}}"`。使用时上传触发器尺寸自适应 slot 内容 | |
||||||
|
|
||||||
|
## CSS 变量 |
||||||
|
|
||||||
|
| 变量名 | 说明 | 默认值 | |
||||||
|
| --- | --- | --- | |
||||||
|
| `--upload-bg` | 上传区域背景色 | `#f7f8fa` | |
||||||
|
| `--upload-border` | 边框颜色 | `#e5e7eb` | |
||||||
|
| `--upload-text` | 主文字颜色 | `#1f2937` | |
||||||
|
| `--upload-text-secondary` | 次要文字颜色 | `#9ca3af` | |
||||||
|
| `--upload-primary` | 主题色 | `#3b82f6` | |
||||||
|
| `--upload-error` | 错误色 | `#ef4444` | |
||||||
|
| `--upload-mask` | 遮罩颜色 | `rgba(0, 0, 0, 0.5)` | |
||||||
|
| `--upload-radius` | 圆角大小 | `16rpx` | |
||||||
|
| `--upload-size` | 文件项尺寸(仅默认上传框生效,slot 模式自适应) | `160rpx` | |
||||||
|
|
||||||
|
## 使用案例 |
||||||
|
|
||||||
|
### 案例 1:使用默认上传框 |
||||||
|
|
||||||
|
```json |
||||||
|
// page.json |
||||||
|
{ |
||||||
|
"usingComponents": { |
||||||
|
"upload-file": "@/components/uploadFile/index" |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- page.wxml --> |
||||||
|
<upload-file |
||||||
|
maxCount="{{3}}" |
||||||
|
maxSize="{{5242880}}" |
||||||
|
accept="{{['image']}}" |
||||||
|
bind:select="onUploadSelect" |
||||||
|
bind:success="onUploadSuccess" |
||||||
|
bind:error="onUploadError" |
||||||
|
bind:remove="onUploadRemove" |
||||||
|
/> |
||||||
|
``` |
||||||
|
|
||||||
|
```ts |
||||||
|
// page.ts |
||||||
|
Page({ |
||||||
|
onUploadSelect(e: WechatMiniprogram.CustomEvent) { |
||||||
|
console.log('选中文件', e.detail.files) |
||||||
|
}, |
||||||
|
onUploadSuccess(e: WechatMiniprogram.CustomEvent) { |
||||||
|
console.log('上传成功', e.detail.file, e.detail.response) |
||||||
|
}, |
||||||
|
onUploadError(e: WechatMiniprogram.CustomEvent) { |
||||||
|
console.log('上传失败', e.detail.file, e.detail.error) |
||||||
|
}, |
||||||
|
onUploadRemove(e: WechatMiniprogram.CustomEvent) { |
||||||
|
console.log('删除文件', e.detail.file, e.detail.fileList) |
||||||
|
}, |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
### 案例 2:使用 slot 自定义上传区域 |
||||||
|
|
||||||
|
```xml |
||||||
|
<!-- page.wxml --> |
||||||
|
<upload-file |
||||||
|
useSlot="{{true}}" |
||||||
|
maxCount="{{5}}" |
||||||
|
accept="{{['image', 'video']}}" |
||||||
|
bind:success="onUploadSuccess" |
||||||
|
> |
||||||
|
<view slot="upload-area" class="custom-upload-area"> |
||||||
|
<image src="/images/upload-icon.png" mode="aspectFit" /> |
||||||
|
<text>点击上传</text> |
||||||
|
</view> |
||||||
|
</upload-file> |
||||||
|
``` |
||||||
|
|
||||||
|
```scss |
||||||
|
// page.scss |
||||||
|
.custom-upload-area { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 12rpx; |
||||||
|
|
||||||
|
image { |
||||||
|
width: 64rpx; |
||||||
|
height: 64rpx; |
||||||
|
} |
||||||
|
|
||||||
|
text { |
||||||
|
font-size: 24rpx; |
||||||
|
color: #9ca3af; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"component": true, |
||||||
|
"usingComponents": {} |
||||||
|
} |
||||||
@ -0,0 +1,272 @@ |
|||||||
|
/** |
||||||
|
* Upload 上传组件样式 |
||||||
|
* 简约通用,颜色通过 CSS 变量定义,方便外部覆盖 |
||||||
|
*/ |
||||||
|
|
||||||
|
/* CSS 变量定义,外部可通过 page 或父级覆盖 */ |
||||||
|
page { |
||||||
|
--upload-bg: #f7f8fa; |
||||||
|
--upload-border: #e5e7eb; |
||||||
|
--upload-text: #1f2937; |
||||||
|
--upload-text-secondary: #9ca3af; |
||||||
|
--upload-primary: #3b82f6; |
||||||
|
--upload-error: #ef4444; |
||||||
|
--upload-mask: rgba(0, 0, 0, 0.5); |
||||||
|
--upload-radius: 16rpx; |
||||||
|
--upload-size: 160rpx; |
||||||
|
--upload-preview-height: 160rpx; // 预览项高度(独立控制) |
||||||
|
} |
||||||
|
|
||||||
|
.upload { |
||||||
|
width: 100%; |
||||||
|
|
||||||
|
.upload-list { |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 16rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.upload-item { |
||||||
|
position: relative; |
||||||
|
width: 100%; |
||||||
|
height: var(--upload-preview-height); |
||||||
|
border-radius: var(--upload-radius); |
||||||
|
overflow: hidden; |
||||||
|
|
||||||
|
&--file { |
||||||
|
width: 100%; |
||||||
|
height: auto; |
||||||
|
min-height: var(--upload-preview-height); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.upload-preview { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
&--file { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 16rpx; |
||||||
|
padding: 20rpx; |
||||||
|
background: var(--upload-bg); |
||||||
|
border-radius: var(--upload-radius); |
||||||
|
} |
||||||
|
|
||||||
|
.upload-preview-media { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
display: block; |
||||||
|
} |
||||||
|
|
||||||
|
.upload-preview-play { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: var(--upload-mask); |
||||||
|
|
||||||
|
.upload-preview-play-icon { |
||||||
|
width: 0; |
||||||
|
height: 0; |
||||||
|
border-style: solid; |
||||||
|
border-width: 16rpx 0 16rpx 28rpx; |
||||||
|
border-color: transparent transparent transparent #fff; |
||||||
|
margin-left: 8rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* 文件类型预览 */ |
||||||
|
.upload-file-icon { |
||||||
|
width: 80rpx; |
||||||
|
height: 96rpx; |
||||||
|
background: #fff; |
||||||
|
border: 2rpx solid var(--upload-border); |
||||||
|
border-radius: 8rpx; |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
align-items: flex-end; |
||||||
|
justify-content: center; |
||||||
|
padding-bottom: 12rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
|
||||||
|
.upload-file-icon-corner { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
width: 24rpx; |
||||||
|
height: 24rpx; |
||||||
|
background: var(--upload-border); |
||||||
|
border-radius: 0 6rpx 0 8rpx; |
||||||
|
} |
||||||
|
|
||||||
|
.upload-file-icon-text { |
||||||
|
font-size: 16rpx; |
||||||
|
color: var(--upload-text-secondary); |
||||||
|
max-width: 60rpx; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.upload-file-name { |
||||||
|
flex: 1; |
||||||
|
font-size: 26rpx; |
||||||
|
color: var(--upload-text); |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
/* 上传中遮罩 */ |
||||||
|
.upload-mask { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
background: var(--upload-mask); |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
gap: 12rpx; |
||||||
|
|
||||||
|
.upload-progress { |
||||||
|
width: 70%; |
||||||
|
height: 6rpx; |
||||||
|
background: rgba(255, 255, 255, 0.3); |
||||||
|
border-radius: 3rpx; |
||||||
|
overflow: hidden; |
||||||
|
|
||||||
|
.upload-progress-bar { |
||||||
|
height: 100%; |
||||||
|
background: #fff; |
||||||
|
border-radius: 3rpx; |
||||||
|
transition: width 0.2s ease; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.upload-progress-text { |
||||||
|
font-size: 24rpx; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
&--error { |
||||||
|
.upload-error-text { |
||||||
|
font-size: 24rpx; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
.upload-retry { |
||||||
|
padding: 8rpx 24rpx; |
||||||
|
border: 2rpx solid #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
|
||||||
|
.upload-retry-text { |
||||||
|
font-size: 24rpx; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* 删除按钮 */ |
||||||
|
.upload-remove { |
||||||
|
position: absolute; |
||||||
|
top: -8rpx; |
||||||
|
right: -8rpx; |
||||||
|
width: 36rpx; |
||||||
|
height: 36rpx; |
||||||
|
background: var(--upload-mask); |
||||||
|
border-radius: 50%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
z-index: 2; |
||||||
|
|
||||||
|
.upload-remove-icon { |
||||||
|
position: relative; |
||||||
|
width: 18rpx; |
||||||
|
height: 18rpx; |
||||||
|
|
||||||
|
&::before, |
||||||
|
&::after { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: 50%; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
height: 2rpx; |
||||||
|
background: #fff; |
||||||
|
transform-origin: center; |
||||||
|
} |
||||||
|
|
||||||
|
&::before { |
||||||
|
transform: translateY(-50%) rotate(45deg); |
||||||
|
} |
||||||
|
|
||||||
|
&::after { |
||||||
|
transform: translateY(-50%) rotate(-45deg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* 上传触发器 */ |
||||||
|
.upload-trigger { |
||||||
|
width: var(--upload-size); |
||||||
|
height: var(--upload-size); |
||||||
|
|
||||||
|
&--slot { |
||||||
|
width: 100%; // slot 模式下宽度撑满父容器 |
||||||
|
height: auto; // 高度自适应 slot 内容 |
||||||
|
} |
||||||
|
|
||||||
|
.upload-trigger-default { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background: var(--upload-bg); |
||||||
|
border-radius: var(--upload-radius); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
border: 2rpx dashed var(--upload-border); |
||||||
|
box-sizing: border-box; |
||||||
|
|
||||||
|
.upload-trigger-icon { |
||||||
|
position: relative; |
||||||
|
width: 48rpx; |
||||||
|
height: 48rpx; |
||||||
|
|
||||||
|
&::before, |
||||||
|
&::after { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
top: 50%; |
||||||
|
left: 50%; |
||||||
|
background: var(--upload-text-secondary); |
||||||
|
border-radius: 2rpx; |
||||||
|
} |
||||||
|
|
||||||
|
&::before { |
||||||
|
width: 48rpx; |
||||||
|
height: 4rpx; |
||||||
|
transform: translate(-50%, -50%); |
||||||
|
} |
||||||
|
|
||||||
|
&::after { |
||||||
|
width: 4rpx; |
||||||
|
height: 48rpx; |
||||||
|
transform: translate(-50%, -50%); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,363 @@ |
|||||||
|
/** |
||||||
|
* Upload 上传组件 |
||||||
|
* 支持图片/视频/文件上传,多文件选择,实时进度条,本地预览,删除附件,失败重试 |
||||||
|
*/ |
||||||
|
|
||||||
|
/** 单个文件对象 */ |
||||||
|
interface UploadFile { |
||||||
|
/** 文件唯一标识 */ |
||||||
|
uid: string |
||||||
|
/** 本地临时路径或上传后的远程 url */ |
||||||
|
url: string |
||||||
|
/** 文件类型: image | video | file */ |
||||||
|
type: string |
||||||
|
/** 文件名 */ |
||||||
|
name: string |
||||||
|
/** 文件大小(byte) */ |
||||||
|
size: number |
||||||
|
/** 上传状态: pending | uploading | success | error */ |
||||||
|
status: 'pending' | 'uploading' | 'success' | 'error' |
||||||
|
/** 上传进度 0-100 */ |
||||||
|
progress: number |
||||||
|
/** 上传任务对象 */ |
||||||
|
_task?: WechatMiniprogram.UploadTask |
||||||
|
} |
||||||
|
|
||||||
|
/** 文件大小格式化 */ |
||||||
|
function formatSize(size: number): string { |
||||||
|
if (size < 1024) return `${size}B` |
||||||
|
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}KB` |
||||||
|
return `${(size / (1024 * 1024)).toFixed(1)}MB` |
||||||
|
} |
||||||
|
|
||||||
|
Component({ |
||||||
|
options: { |
||||||
|
multipleSlots: true, |
||||||
|
styleIsolation: 'shared', // 允许外部样式影响组件内部
|
||||||
|
}, |
||||||
|
|
||||||
|
properties: { |
||||||
|
/** 最大上传文件数,默认 9 */ |
||||||
|
maxCount: { |
||||||
|
type: Number, |
||||||
|
value: 9, |
||||||
|
}, |
||||||
|
/** 单文件最大限制(byte),默认 10MB */ |
||||||
|
maxSize: { |
||||||
|
type: Number, |
||||||
|
value: 10 * 1024 * 1024, |
||||||
|
}, |
||||||
|
/** 允许的文件类型数组: 'image' | 'video' | 'file' */ |
||||||
|
accept: { |
||||||
|
type: Array, |
||||||
|
value: ['image'], |
||||||
|
}, |
||||||
|
/** 自定义文件后缀(仅 accept 含 file 时生效),如 ['.pdf', '.doc', '.docx'] */ |
||||||
|
extensions: { |
||||||
|
type: Array, |
||||||
|
value: [], |
||||||
|
}, |
||||||
|
/** 只读模式(不显示上传和删除按钮) */ |
||||||
|
readonly: { |
||||||
|
type: Boolean, |
||||||
|
value: false, |
||||||
|
}, |
||||||
|
/** 是否使用自定义上传区域插槽 */ |
||||||
|
useSlot: { |
||||||
|
type: Boolean, |
||||||
|
value: false, |
||||||
|
}, |
||||||
|
/** 已有文件列表(用于回显) */ |
||||||
|
fileList: { |
||||||
|
type: Array, |
||||||
|
value: [], |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
data: { |
||||||
|
/** 内部文件列表 */ |
||||||
|
_fileList: [] as UploadFile[], |
||||||
|
}, |
||||||
|
|
||||||
|
lifetimes: { |
||||||
|
attached() { |
||||||
|
// 初始化已有文件列表
|
||||||
|
if (this.properties.fileList.length > 0) { |
||||||
|
this.setData({ |
||||||
|
_fileList: this.properties.fileList.map((item: any) => ({ |
||||||
|
uid: item.uid || `init_${Date.now()}_${Math.random()}`, |
||||||
|
url: item.url, |
||||||
|
type: item.type || 'image', |
||||||
|
name: item.name || '', |
||||||
|
size: item.size || 0, |
||||||
|
status: 'success', |
||||||
|
progress: 100, |
||||||
|
})), |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
/** |
||||||
|
* 选择文件 |
||||||
|
*/ |
||||||
|
onChooseFile() { |
||||||
|
if (this.data.readonly) return |
||||||
|
const { _fileList, maxCount } = this.data |
||||||
|
const remaining = maxCount - _fileList.length |
||||||
|
if (remaining <= 0) { |
||||||
|
wx.showToast({ title: `最多上传${maxCount}个文件`, icon: 'none' }) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const accept = this.properties.accept as string[] |
||||||
|
// 如果包含 image 或 video,使用 wx.chooseMedia
|
||||||
|
const hasImage = accept.includes('image') |
||||||
|
const hasVideo = accept.includes('video') |
||||||
|
const hasFile = accept.includes('file') |
||||||
|
|
||||||
|
if (hasFile && !hasImage && !hasVideo) { |
||||||
|
// 纯文件上传
|
||||||
|
this.chooseFileOnly(remaining) |
||||||
|
} else if (hasImage || hasVideo) { |
||||||
|
// 图片/视频
|
||||||
|
this.chooseMedia(remaining, hasImage, hasVideo, hasFile) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 选择图片/视频 |
||||||
|
*/ |
||||||
|
chooseMedia(remaining: number, hasImage: boolean, hasVideo: boolean, hasFile: boolean) { |
||||||
|
const mediaType: string[] = [] |
||||||
|
if (hasImage) mediaType.push('image') |
||||||
|
if (hasVideo) mediaType.push('video') |
||||||
|
|
||||||
|
wx.chooseMedia({ |
||||||
|
count: remaining, |
||||||
|
mediaType: mediaType as ('image' | 'video')[], |
||||||
|
sourceType: ['album', 'camera'], |
||||||
|
camera: 'back', |
||||||
|
success: (res) => { |
||||||
|
const files = res.tempFiles.map((f) => { |
||||||
|
const isVideo = f.fileType === 'video' |
||||||
|
return { |
||||||
|
uid: `file_${Date.now()}_${Math.random()}`, |
||||||
|
url: f.tempFilePath, |
||||||
|
type: isVideo ? 'video' : 'image', |
||||||
|
name: f.tempFilePath.split('/').pop() || '', |
||||||
|
size: f.size, |
||||||
|
status: 'pending' as const, |
||||||
|
progress: 0, |
||||||
|
} |
||||||
|
}) |
||||||
|
this.afterChoose(files) |
||||||
|
}, |
||||||
|
fail: (err) => { |
||||||
|
if (!err.errMsg.includes('cancel')) { |
||||||
|
wx.showToast({ title: '选择文件失败', icon: 'none' }) |
||||||
|
} |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
// 如果同时支持文件,提示用户
|
||||||
|
if (hasFile) { |
||||||
|
// 在 chooseMedia 之后无法同时选择文件,这里不做额外处理
|
||||||
|
// 如果需要同时支持,用户可分多次选择
|
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 仅选择文件 |
||||||
|
*/ |
||||||
|
chooseFileOnly(remaining: number) { |
||||||
|
const extensions = this.properties.extensions as string[] |
||||||
|
wx.chooseMessageFile({ |
||||||
|
count: remaining, |
||||||
|
type: extensions.length > 0 ? 'file' : 'file', |
||||||
|
extension: extensions.length > 0 ? extensions : undefined, |
||||||
|
success: (res) => { |
||||||
|
const files: UploadFile[] = res.tempFiles.map((f) => ({ |
||||||
|
uid: `file_${Date.now()}_${Math.random()}`, |
||||||
|
url: f.path, |
||||||
|
type: 'file', |
||||||
|
name: f.name, |
||||||
|
size: f.size, |
||||||
|
status: 'pending', |
||||||
|
progress: 0, |
||||||
|
})) |
||||||
|
this.afterChoose(files) |
||||||
|
}, |
||||||
|
fail: (err) => { |
||||||
|
if (!err.errMsg.includes('cancel')) { |
||||||
|
wx.showToast({ title: '选择文件失败', icon: 'none' }) |
||||||
|
} |
||||||
|
}, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 选择文件后的校验和上传 |
||||||
|
*/ |
||||||
|
afterChoose(files: UploadFile[]) { |
||||||
|
// 校验文件大小
|
||||||
|
const validFiles: UploadFile[] = [] |
||||||
|
const maxSize = this.properties.maxSize |
||||||
|
for (const file of files) { |
||||||
|
if (file.size > maxSize) { |
||||||
|
wx.showToast({ |
||||||
|
title: `${file.name || '文件'}超过${formatSize(maxSize)}`, |
||||||
|
icon: 'none', |
||||||
|
}) |
||||||
|
continue |
||||||
|
} |
||||||
|
validFiles.push(file) |
||||||
|
} |
||||||
|
|
||||||
|
if (validFiles.length === 0) return |
||||||
|
|
||||||
|
// 触发 onSelect 事件
|
||||||
|
this.triggerEvent('select', { files: validFiles }) |
||||||
|
|
||||||
|
// 更新文件列表
|
||||||
|
const newList = [...this.data._fileList, ...validFiles] |
||||||
|
this.setData({ _fileList: newList }) |
||||||
|
|
||||||
|
// 逐个上传
|
||||||
|
validFiles.forEach((file) => { |
||||||
|
this.uploadFile(file) |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 上传单个文件 |
||||||
|
*/ |
||||||
|
uploadFile(file: UploadFile) { |
||||||
|
const app = getApp<IAppOption>() |
||||||
|
const action = app.globalData.upFileUrl |
||||||
|
const loginState = app.globalData.loginState |
||||||
|
|
||||||
|
this.updateFile(file.uid, { status: 'uploading', progress: 0 }) |
||||||
|
|
||||||
|
const task = wx.uploadFile({ |
||||||
|
url: action, |
||||||
|
filePath: file.url, |
||||||
|
name: 'file', |
||||||
|
header: { loginState }, |
||||||
|
formData: { loginState }, |
||||||
|
success: (res) => { |
||||||
|
// 尝试解析返回数据
|
||||||
|
let data: any |
||||||
|
try { |
||||||
|
data = JSON.parse(res.data) |
||||||
|
} catch { |
||||||
|
data = res.data |
||||||
|
} |
||||||
|
this.updateFile(file.uid, { |
||||||
|
status: 'success', |
||||||
|
progress: 100, |
||||||
|
url: data.url || data.data?.url || file.url, |
||||||
|
}) |
||||||
|
this.triggerEvent('success', { file: this.getFile(file.uid), response: data }) |
||||||
|
}, |
||||||
|
fail: (err) => { |
||||||
|
this.updateFile(file.uid, { status: 'error', progress: 0 }) |
||||||
|
this.triggerEvent('error', { file: this.getFile(file.uid), error: err }) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
// 监听上传进度
|
||||||
|
task.onProgressUpdate((res) => { |
||||||
|
this.updateFile(file.uid, { progress: res.progress }) |
||||||
|
}) |
||||||
|
|
||||||
|
// 保存 task 引用
|
||||||
|
this.updateFile(file.uid, { _task: task }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 重试上传 |
||||||
|
*/ |
||||||
|
onRetry(e: WechatMiniprogram.BaseEvent) { |
||||||
|
const { uid } = e.currentTarget.dataset |
||||||
|
const file = this.getFile(uid) |
||||||
|
if (file && file.status === 'error') { |
||||||
|
this.uploadFile(file) |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 删除文件 |
||||||
|
*/ |
||||||
|
onRemove(e: WechatMiniprogram.BaseEvent) { |
||||||
|
const { uid } = e.currentTarget.dataset |
||||||
|
const file = this.getFile(uid) |
||||||
|
if (!file) return |
||||||
|
|
||||||
|
// 取消上传任务
|
||||||
|
if (file._task) { |
||||||
|
file._task.abort() |
||||||
|
} |
||||||
|
|
||||||
|
const newList = this.data._fileList.filter((f) => f.uid !== uid) |
||||||
|
this.setData({ _fileList: newList }) |
||||||
|
this.triggerEvent('remove', { file, fileList: newList }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 预览图片 |
||||||
|
*/ |
||||||
|
onPreviewImage(e: WechatMiniprogram.BaseEvent) { |
||||||
|
const { uid } = e.currentTarget.dataset |
||||||
|
const file = this.getFile(uid) |
||||||
|
if (!file) return |
||||||
|
|
||||||
|
const imageList = this.data._fileList.filter((f) => f.type === 'image').map((f) => f.url) |
||||||
|
wx.previewImage({ |
||||||
|
current: file.url, |
||||||
|
urls: imageList, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 预览视频 |
||||||
|
*/ |
||||||
|
onPreviewVideo(e: WechatMiniprogram.BaseEvent) { |
||||||
|
const { uid } = e.currentTarget.dataset |
||||||
|
const file = this.getFile(uid) |
||||||
|
if (!file) return |
||||||
|
wx.previewMedia({ |
||||||
|
sources: [{ url: file.url, type: 'video' }], |
||||||
|
current: 0, |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 更新单个文件状态 |
||||||
|
*/ |
||||||
|
updateFile(uid: string, patch: Partial<UploadFile>) { |
||||||
|
const list = this.data._fileList.map((f) => { |
||||||
|
if (f.uid === uid) { |
||||||
|
return { ...f, ...patch } |
||||||
|
} |
||||||
|
return f |
||||||
|
}) |
||||||
|
this.setData({ _fileList: list }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取单个文件 |
||||||
|
*/ |
||||||
|
getFile(uid: string): UploadFile | undefined { |
||||||
|
return this.data._fileList.find((f) => f.uid === uid) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取当前文件列表(供外部调用) |
||||||
|
*/ |
||||||
|
getFileList(): UploadFile[] { |
||||||
|
return this.data._fileList |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
<!-- |
||||||
|
Upload 上传组件 |
||||||
|
支持图片/视频/文件上传,进度条,预览,删除,重试 |
||||||
|
--> |
||||||
|
<view class="upload"> |
||||||
|
<!-- 文件列表 --> |
||||||
|
<view class="upload-list"> |
||||||
|
<!-- 单个文件项 --> |
||||||
|
<view |
||||||
|
wx:for="{{_fileList}}" |
||||||
|
wx:key="uid" |
||||||
|
class="upload-item upload-item--{{item.type}}" |
||||||
|
> |
||||||
|
<!-- 图片预览 --> |
||||||
|
<view |
||||||
|
wx:if="{{item.type === 'image'}}" |
||||||
|
class="upload-preview" |
||||||
|
data-uid="{{item.uid}}" |
||||||
|
bindtap="onPreviewImage" |
||||||
|
> |
||||||
|
<image class="upload-preview-media" src="{{item.url}}" mode="aspectFill" /> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 视频预览 --> |
||||||
|
<view |
||||||
|
wx:elif="{{item.type === 'video'}}" |
||||||
|
class="upload-preview" |
||||||
|
data-uid="{{item.uid}}" |
||||||
|
bindtap="onPreviewVideo" |
||||||
|
> |
||||||
|
<video |
||||||
|
class="upload-preview-media" |
||||||
|
src="{{item.url}}" |
||||||
|
controls="{{false}}" |
||||||
|
object-fit="cover" |
||||||
|
/> |
||||||
|
<view class="upload-preview-play"> |
||||||
|
<view class="upload-preview-play-icon"></view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 文件预览 --> |
||||||
|
<view wx:elif="{{item.type === 'file'}}" class="upload-preview upload-preview--file"> |
||||||
|
<view class="upload-file-icon"> |
||||||
|
<view class="upload-file-icon-corner"></view> |
||||||
|
<text class="upload-file-icon-text">{{item.name}}</text> |
||||||
|
</view> |
||||||
|
<text class="upload-file-name">{{item.name}}</text> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 上传中遮罩 + 进度条 --> |
||||||
|
<view wx:if="{{item.status === 'uploading'}}" class="upload-mask"> |
||||||
|
<view class="upload-progress"> |
||||||
|
<view class="upload-progress-bar" style="width: {{item.progress}}%"></view> |
||||||
|
</view> |
||||||
|
<text class="upload-progress-text">{{item.progress}}%</text> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 上传失败遮罩 + 重试 --> |
||||||
|
<view wx:if="{{item.status === 'error'}}" class="upload-mask upload-mask--error"> |
||||||
|
<text class="upload-error-text">上传失败</text> |
||||||
|
<view class="upload-retry" data-uid="{{item.uid}}" catchtap="onRetry"> |
||||||
|
<text class="upload-retry-text">重试</text> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 删除按钮 --> |
||||||
|
<view |
||||||
|
wx:if="{{!readonly}}" |
||||||
|
class="upload-remove" |
||||||
|
data-uid="{{item.uid}}" |
||||||
|
catchtap="onRemove" |
||||||
|
> |
||||||
|
<view class="upload-remove-icon"></view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 默认上传按钮(未达到最大数量且非只读时显示) --> |
||||||
|
<view |
||||||
|
wx:if="{{!readonly && _fileList.length < maxCount}}" |
||||||
|
class="upload-trigger {{useSlot ? 'upload-trigger--slot' : ''}}" |
||||||
|
bindtap="onChooseFile" |
||||||
|
> |
||||||
|
<!-- 具名插槽:自定义上传区域 --> |
||||||
|
<slot name="upload-area"></slot> |
||||||
|
<!-- 默认上传占位 --> |
||||||
|
<view wx:if="{{!useSlot}}" class="upload-trigger-default"> |
||||||
|
<view class="upload-trigger-icon"></view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 880 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 875 B |
|
After Width: | Height: | Size: 1.4 KiB |
@ -1,5 +1,7 @@ |
|||||||
{ |
{ |
||||||
"navigationBarTitleText": "创建活动", |
"navigationBarTitleText": "创建活动", |
||||||
"navigationStyle": "default", |
"navigationStyle": "default", |
||||||
"usingComponents": {} |
"usingComponents": { |
||||||
|
"upload-file": "/components/uploadFile/index" |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"navigationStyle": "default", |
||||||
|
"navigationBarTitleText": "智能体", |
||||||
|
"usingComponents": { |
||||||
|
"van-icon": "@vant/weapp/icon/index", |
||||||
|
"van-tab": "@vant/weapp/tab/index", |
||||||
|
"van-tabs": "@vant/weapp/tabs/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,250 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
padding-bottom: calc(60px + env(safe-area-inset-bottom)); |
||||||
|
.page-header { |
||||||
|
padding: 20rpx 32rpx; |
||||||
|
border-radius: 0 0 32rpx 32rpx; |
||||||
|
background-color: #fff; |
||||||
|
.search { |
||||||
|
padding: 12rpx 24rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8rpx; |
||||||
|
border-radius: 46rpx; |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
.icon { |
||||||
|
width: 40rpx; |
||||||
|
height: 40rpx; |
||||||
|
} |
||||||
|
.input { |
||||||
|
flex: 1; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
font-size: 28rpx; |
||||||
|
} |
||||||
|
.input-place { |
||||||
|
color: rgba(148, 163, 184, 0.7); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.page-container { |
||||||
|
padding: 24rpx 32rpx; |
||||||
|
.banner { |
||||||
|
.swiper { |
||||||
|
width: 100%; |
||||||
|
height: 229rpx; |
||||||
|
} |
||||||
|
.s-img { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
border-radius: 24rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.kkd { |
||||||
|
margin-top: 24rpx; |
||||||
|
padding: 32rpx; |
||||||
|
background: linear-gradient(180deg, #f2faff 0%, #ffffff 27.29%, #ffffff 100%); |
||||||
|
border-radius: 24rpx 24rpx 24rpx 24rpx; |
||||||
|
border: 2rpx solid #ffffff; |
||||||
|
.k-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.name { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.more { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 0.5); |
||||||
|
} |
||||||
|
} |
||||||
|
.k-body { |
||||||
|
margin-top: 24rpx; |
||||||
|
overflow-x: auto; |
||||||
|
display: flex; |
||||||
|
flex-wrap: nowrap; |
||||||
|
gap: 10rpx; |
||||||
|
&::-webkit-scrollbar { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.k-item { |
||||||
|
padding: 0 10rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 170rpx; |
||||||
|
text-align: center; |
||||||
|
box-sizing: border-box; |
||||||
|
.icon { |
||||||
|
width: 104rpx; |
||||||
|
height: 104rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
font-size: 32rpx; |
||||||
|
line-height: 42rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.kkd2 { |
||||||
|
margin-top: 30rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(2, 1fr); |
||||||
|
gap: 22rpx; |
||||||
|
.k-row { |
||||||
|
padding: 24rpx; |
||||||
|
background: linear-gradient(358.94deg, #ffffff 1.04%, #ffffff 59.35%, #f0faff 96.54%); |
||||||
|
border-radius: 24rpx; |
||||||
|
overflow: hidden; |
||||||
|
.kr-header { |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.kr-body { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(3, 1fr); |
||||||
|
.kr-item { |
||||||
|
padding: 0 10rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
text-align: center; |
||||||
|
box-sizing: border-box; |
||||||
|
.icon { |
||||||
|
width: 75rpx; |
||||||
|
height: 75rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
margin-top: 15rpx; |
||||||
|
font-size: 20rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tabs { |
||||||
|
.nav-class { |
||||||
|
background: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.tab-class { |
||||||
|
font-size: 32rpx; |
||||||
|
} |
||||||
|
.tab-active-class { |
||||||
|
font-size: 36rpx; |
||||||
|
} |
||||||
|
.van-tabs__line { |
||||||
|
width: 32rpx !important; |
||||||
|
height: 6rpx !important; |
||||||
|
} |
||||||
|
.list { |
||||||
|
margin-top: 24rpx; |
||||||
|
padding: 22rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
.list-card { |
||||||
|
display: flex; |
||||||
|
align-content: inherit; |
||||||
|
padding-bottom: 34rpx; |
||||||
|
border-bottom: 1px solid rgba(248, 250, 252, 1); |
||||||
|
margin-bottom: 32rpx; |
||||||
|
&:last-of-type { |
||||||
|
border: none; |
||||||
|
margin-bottom: 0; |
||||||
|
} |
||||||
|
.order { |
||||||
|
flex-shrink: 0; |
||||||
|
padding-top: 54rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
min-width: 50rpx; |
||||||
|
.icon { |
||||||
|
width: 50rpx; |
||||||
|
height: 50rpx; |
||||||
|
} |
||||||
|
.num { |
||||||
|
text-align: center; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.photo { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 158rpx; |
||||||
|
height: 158rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
.p-img { |
||||||
|
width: 136rpx; |
||||||
|
height: 136rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
flex: 1; |
||||||
|
min-width: 0; |
||||||
|
padding-top: 10rpx; |
||||||
|
padding-left: 13rpx; |
||||||
|
.title { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
white-space: nowrap; |
||||||
|
text-overflow: ellipsis; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
.stat { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 22rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
.options { |
||||||
|
margin-top: 32rpx; |
||||||
|
display: flex; |
||||||
|
justify-content: flex-end; |
||||||
|
gap: 15rpx; |
||||||
|
.o-item { |
||||||
|
padding: 8rpx 22rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 40rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
line-height: 32rpx; |
||||||
|
.icon { |
||||||
|
width: 24rpx; |
||||||
|
height: 24rpx; |
||||||
|
} |
||||||
|
.icon:last-of-type { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
&.active { |
||||||
|
background-color: rgba(74, 184, 253, 0.1); |
||||||
|
.icon { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.icon:last-of-type { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
const _app = getApp<IAppOption>() |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
handleEva() { |
||||||
|
wx.navigateTo({ |
||||||
|
url: '/pages/agentEva/index', |
||||||
|
}) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,112 @@ |
|||||||
|
<view class="page"> |
||||||
|
<view class="page-header"> |
||||||
|
<view class="search"> |
||||||
|
<image class="icon" src="/images/icon47.png"></image> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="input-place" |
||||||
|
type="text" |
||||||
|
placeholder="请搜索你想要的内容" |
||||||
|
confirm-type="search" |
||||||
|
/> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="page-container"> |
||||||
|
<view class="banner"> |
||||||
|
<swiper |
||||||
|
class="swiper" |
||||||
|
indicator-dots |
||||||
|
indicator-color="rgba(255, 255, 255, 1)" |
||||||
|
indicator-active-color="rgba(74, 184, 253, 1)" |
||||||
|
> |
||||||
|
<swiper-item class="swiper-item"> |
||||||
|
<image class="s-img" mode="aspectFill" src="/images/bg1.png"></image> |
||||||
|
</swiper-item> |
||||||
|
</swiper> |
||||||
|
</view> |
||||||
|
<view class="kkd"> |
||||||
|
<view class="k-header"> |
||||||
|
<view class="name">推荐智能体</view> |
||||||
|
<view class="more"> |
||||||
|
更多 |
||||||
|
<van-icon name="arrow" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="k-body"> |
||||||
|
<view class="k-item" wx:for="{{4}}" wx:key="index"> |
||||||
|
<image class="icon" src="/images/icon2.png"></image> |
||||||
|
<view class="name">PPT小助手</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="kkd2"> |
||||||
|
<view class="k-row"> |
||||||
|
<view class="kr-header">最近使用</view> |
||||||
|
<view class="kr-body"> |
||||||
|
<view class="kr-item" wx:for="{{3}}" wx:key="index"> |
||||||
|
<image class="icon" src="/images/icon2.png"></image> |
||||||
|
<view class="name">PPT小助</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="k-row"> |
||||||
|
<view class="kr-header">我的收藏</view> |
||||||
|
<view class="kr-body"> |
||||||
|
<view class="kr-item" wx:for="{{3}}" wx:key="index"> |
||||||
|
<image class="icon" src="/images/icon2.png"></image> |
||||||
|
<view class="name">文章去</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<van-tabs |
||||||
|
class="tabs" |
||||||
|
nav-class="nav-class" |
||||||
|
tab-class="tab-class" |
||||||
|
tab-active-class="tab-active-class" |
||||||
|
color="rgba(74, 184, 253, 1)" |
||||||
|
title-active-color="rgba(74, 184, 253, 1)" |
||||||
|
title-inactive-color="rgba(71, 85, 105, 1)" |
||||||
|
active="{{ active }}" |
||||||
|
bind:change="onChange" |
||||||
|
> |
||||||
|
<van-tab title="全部"> |
||||||
|
<view class="list"> |
||||||
|
<view class="list-card" wx:for="{{10}}" wx:key="index"> |
||||||
|
<view class="order"> |
||||||
|
<image class="icon" wx:if="{{index==0}}" src="/images/icon48.png"></image> |
||||||
|
<image class="icon" wx:elif="{{index==1}}" src="/images/icon49.png"></image> |
||||||
|
<image class="icon" wx:elif="{{index==2}}" src="/images/icon50.png"></image> |
||||||
|
<view class="num" wx:else>{{index+1}}</view> |
||||||
|
</view> |
||||||
|
<view class="photo"> |
||||||
|
<image class="p-img" src="/images/icon2.png"></image> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="title">应用包生成助手</view> |
||||||
|
<view class="content">智能规划课表,轻松掌握学习轻松掌握学习</view> |
||||||
|
<view class="stat">15.6w人使用</view> |
||||||
|
<view class="options"> |
||||||
|
<view class="o-item active" bind:tap="handleEva"> |
||||||
|
<image class="icon" src="/images/icon55.png"></image> |
||||||
|
<image class="icon" src="/images/icon51.png"></image> |
||||||
|
评级 |
||||||
|
</view> |
||||||
|
<view class="o-item"> |
||||||
|
<image class="icon" src="/images/icon56.png"></image> |
||||||
|
<image class="icon" src="/images/icon57.png"></image> |
||||||
|
收藏 |
||||||
|
</view> |
||||||
|
<view class="o-item">使用</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</van-tab> |
||||||
|
<van-tab title="全部">全部</van-tab> |
||||||
|
<van-tab title="全部">全部</van-tab> |
||||||
|
<van-tab title="全部">全部</van-tab> |
||||||
|
<van-tab title="全部">全部</van-tab> |
||||||
|
</van-tabs> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"navigationStyle": "default", |
||||||
|
"navigationBarTitleText": "智能体", |
||||||
|
"usingComponents": { |
||||||
|
"van-rate": "@vant/weapp/rate/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,219 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(248, 246, 246, 1); |
||||||
|
} |
||||||
|
.page { |
||||||
|
padding-bottom: 420rpx; |
||||||
|
.page-header { |
||||||
|
padding: 40rpx; |
||||||
|
background-color: #fff; |
||||||
|
.logo { |
||||||
|
display: block; |
||||||
|
margin: 0 auto; |
||||||
|
width: 160rpx; |
||||||
|
height: 160rpx; |
||||||
|
box-shadow: 0px 6.7px 10.04px -6.7px rgba(0, 0, 0, 0.1); |
||||||
|
box-shadow: 0px 16.74px 25.11px -5.02px rgba(0, 0, 0, 0.1); |
||||||
|
border-radius: 24rpx; |
||||||
|
} |
||||||
|
.title { |
||||||
|
margin-top: 26rpx; |
||||||
|
text-align: center; |
||||||
|
font-size: 40rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 6rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(107, 114, 128, 1); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.rate-wrap { |
||||||
|
margin-top: 76rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.num { |
||||||
|
font-size: 96rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
.stat { |
||||||
|
margin-top: 5rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(107, 114, 128, 1); |
||||||
|
text-align: right; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.list { |
||||||
|
margin: 48rpx 30rpx; |
||||||
|
.list-title { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.list-card { |
||||||
|
margin: 30rpx 0 0; |
||||||
|
padding: 32rpx; |
||||||
|
background-color: rgba(255, 255, 255, 1); |
||||||
|
border-radius: 32rpx; |
||||||
|
.user { |
||||||
|
display: flex; |
||||||
|
gap: 20rpx; |
||||||
|
.avatar { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 89rpx; |
||||||
|
height: 89rpx; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
flex: 1; |
||||||
|
padding: 7rpx 20rpx 0; |
||||||
|
.w-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 13rpx; |
||||||
|
.name { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.idenity { |
||||||
|
padding: 6rpx 11rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
line-height: 1; |
||||||
|
border-radius: 7rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
background-color: rgba(74, 184, 253, 0.1); |
||||||
|
&.teacher { |
||||||
|
background-color: rgba(254, 181, 74, 0.1); |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.date { |
||||||
|
margin-top: 6rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(107, 114, 128, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.rate-num { |
||||||
|
align-self: start; |
||||||
|
padding: 4rpx 14rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
font-weight: bold; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
background: rgba(74, 184, 253, 0.1); |
||||||
|
border-radius: 13rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 22rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(31, 41, 55, 1); |
||||||
|
line-height: 42rpx; |
||||||
|
} |
||||||
|
.l-footer { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.item { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 12rpx; |
||||||
|
.icon, |
||||||
|
.icon-active { |
||||||
|
width: 36rpx; |
||||||
|
height: 36rpx; |
||||||
|
} |
||||||
|
.icon-active { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.i-content { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(156, 163, 175, 1); |
||||||
|
} |
||||||
|
&.active { |
||||||
|
.icon { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.icon-active { |
||||||
|
display: block; |
||||||
|
} |
||||||
|
.i-content { |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.page-footer { |
||||||
|
position: fixed; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
background: #fff; |
||||||
|
border-radius: 40rpx 40rpx 0 0; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 32rpx 32rpx calc(env(safe-area-inset-bottom) + 32rpx); |
||||||
|
.title { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.rate-wrap { |
||||||
|
margin-top: 19rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.rate { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 36rpx; |
||||||
|
.num { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.r-status { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.freetext { |
||||||
|
margin-top: 48rpx; |
||||||
|
display: flex; |
||||||
|
gap: 16rpx; |
||||||
|
.txa { |
||||||
|
padding: 26rpx 32rpx; |
||||||
|
height: 96rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
box-sizing: border-box; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
max-height: 400rpx; |
||||||
|
} |
||||||
|
.txa-place { |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
.btn { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 130rpx; |
||||||
|
height: 96rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: rgba(74, 184, 253, 1); |
||||||
|
box-shadow: 0rpx 15rpx 30rpx -6rpx rgba(0, 96, 143, 0.09); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
const _app = getApp<IAppOption>(); |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
}); |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
<view class="page"> |
||||||
|
<view class="page-header"> |
||||||
|
<view class="logo"> |
||||||
|
<image class="logo" src="/images/icon2.png"></image> |
||||||
|
</view> |
||||||
|
<view class="title">应用包生成助手</view> |
||||||
|
<view class="content">智能应用包构建工具 · 版本 2.4.0</view> |
||||||
|
<view class="rate-wrap"> |
||||||
|
<view class="num">3.5</view> |
||||||
|
<view class="wrap"> |
||||||
|
<van-rate |
||||||
|
value="{{4.5}}" |
||||||
|
color="#F7B550" |
||||||
|
allow-half |
||||||
|
readonly |
||||||
|
size="54rpx" |
||||||
|
void-color="#F7F8FA" |
||||||
|
void-icon="star" |
||||||
|
/> |
||||||
|
<view class="stat">共 7 条用户评价</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="list"> |
||||||
|
<view class="list-title">全部评价</view> |
||||||
|
<view class="list-card"> |
||||||
|
<view class="user"> |
||||||
|
<image class="avatar" src="/images/icon2.png"></image> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="w-header"> |
||||||
|
<view class="name">张同学</view> |
||||||
|
<view class="idenity">学生</view> |
||||||
|
<view class="idenity teacher">教职工</view> |
||||||
|
</view> |
||||||
|
<view class="date">信息工程学院 · 2小时前</view> |
||||||
|
</view> |
||||||
|
<view class="rate-num">3.5</view> |
||||||
|
</view> |
||||||
|
<view class="content"> |
||||||
|
这个生成助手真的帮了大忙!界面直观,配置流程非常清晰,大大缩短了我们项目部署的时间。建议以后可以增加更多自定义模板。 |
||||||
|
</view> |
||||||
|
<view class="l-footer"> |
||||||
|
<view class="item"> |
||||||
|
<image class="icon" src="/images/icon52.png"></image> |
||||||
|
<image class="icon-active" src="/images/icon53.png"></image> |
||||||
|
<view class="i-content">34</view> |
||||||
|
</view> |
||||||
|
<view class="item"> |
||||||
|
<image class="icon" src="/images/icon54.png"></image> |
||||||
|
<view class="i-content">回复</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="page-footer"> |
||||||
|
<view class="title">评分</view> |
||||||
|
<view class="rate-wrap"> |
||||||
|
<view class="rate"> |
||||||
|
<van-rate |
||||||
|
value="{{4.5}}" |
||||||
|
color="#F7B550" |
||||||
|
allow-half |
||||||
|
readonly |
||||||
|
size="54rpx" |
||||||
|
void-color="#F7F8FA" |
||||||
|
void-icon="star" |
||||||
|
/> |
||||||
|
<view class="num">3.5</view> |
||||||
|
</view> |
||||||
|
<view class="r-status">非常满意</view> |
||||||
|
</view> |
||||||
|
<view class="freetext"> |
||||||
|
<textarea class="txa" placeholder="写下你的评价..." auto-height maxlength="-1" placeholder-class="txa-place"></textarea> |
||||||
|
<view class="btn">发布</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "校园巴士", |
||||||
|
"navigationStyle": "default", |
||||||
|
"usingComponents": { |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,314 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.page { |
||||||
|
padding-bottom: calc(120px + env(safe-area-inset-bottom)); |
||||||
|
.map { |
||||||
|
position: relative; |
||||||
|
width: 100%; |
||||||
|
height: 584rpx; |
||||||
|
.mark { |
||||||
|
.mark-container { |
||||||
|
padding: 16rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: #fff; |
||||||
|
display: flex; |
||||||
|
.icon { |
||||||
|
width: 36rpx; |
||||||
|
height: 36rpx; |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
padding-left: 10rpx; |
||||||
|
.title { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 10rpx; |
||||||
|
font-size: 22rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tril { |
||||||
|
margin: -10rpx auto 0; |
||||||
|
display: block; |
||||||
|
width: 0; |
||||||
|
height: 0; |
||||||
|
border-style: solid; |
||||||
|
border-width: 26rpx 26rpx 0 26rpx; |
||||||
|
border-color: #fff transparent transparent transparent; |
||||||
|
} |
||||||
|
} |
||||||
|
.to-center { |
||||||
|
position: absolute; |
||||||
|
top: 337rpx; |
||||||
|
right: 36rpx; |
||||||
|
width: 64rpx; |
||||||
|
height: 64rpx; |
||||||
|
background: #ffffff; |
||||||
|
box-shadow: 0rpx 15rpx 30rpx 0rpx rgba(0, 96, 143, 0.09); |
||||||
|
border-radius: 12rpx 12rpx 12rpx 12rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
.icon { |
||||||
|
width: 34rpx; |
||||||
|
height: 34rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.banner { |
||||||
|
position: relative; |
||||||
|
margin: -140rpx 30rpx 0; |
||||||
|
padding: 32rpx; |
||||||
|
border-radius: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
z-index: 1; |
||||||
|
.b-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.icon1 { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 88rpx; |
||||||
|
height: 88rpx; |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
.w-header { |
||||||
|
.name { |
||||||
|
margin-right: 15rpx; |
||||||
|
display: inline-flex; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
gap: 10rpx; |
||||||
|
align-items: center; |
||||||
|
flex-wrap: wrap; |
||||||
|
.icon2 { |
||||||
|
width: 37rpx; |
||||||
|
height: 21rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 8rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(100, 116, 139, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tip { |
||||||
|
margin-top: 32rpx; |
||||||
|
padding: 16rpx 24rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
background-color: rgba(254, 181, 74, 0.1); |
||||||
|
gap: 16rpx; |
||||||
|
.icon { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 36rpx; |
||||||
|
height: 36rpx; |
||||||
|
} |
||||||
|
.content { |
||||||
|
flex: 1; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
.cross { |
||||||
|
font-size: 20rpx; |
||||||
|
color: rgba(254, 181, 74, 0.49); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.container { |
||||||
|
margin: 24rpx 30rpx 0; |
||||||
|
padding: 30rpx; |
||||||
|
background-color: rgba(255, 255, 255, 1); |
||||||
|
border-radius: 24rpx; |
||||||
|
.next { |
||||||
|
padding: 32rpx; |
||||||
|
background-color: rgba(74, 184, 253, 0.1); |
||||||
|
border-radius: 16rpx; |
||||||
|
.n-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
flex-wrap: wrap; |
||||||
|
.title { |
||||||
|
font-size: 40rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
.high { |
||||||
|
display: inline; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.tag { |
||||||
|
padding: 14rpx 26rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 40rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
line-height: 32rpx; |
||||||
|
.icon { |
||||||
|
width: 23rpx; |
||||||
|
height: 26rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 15rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(100, 116, 139, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.scroll { |
||||||
|
margin: 0 -24rpx; |
||||||
|
padding-top: 88rpx; |
||||||
|
display: flex; |
||||||
|
flex-wrap: nowrap; |
||||||
|
overflow-x: auto; |
||||||
|
&::-webkit-scrollbar { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.item { |
||||||
|
flex-shrink: 0; |
||||||
|
.top { |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
.badge { |
||||||
|
position: absolute; |
||||||
|
top: -73rpx; |
||||||
|
width: 56rpx; |
||||||
|
height: 56rpx; |
||||||
|
border-radius: 12rpx; |
||||||
|
background: linear-gradient(90deg, #9ddffd 0%, #4ab8fd 100%); |
||||||
|
display: none; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
box-shadow: 0px 11.2px 28px 0px rgba(0, 96, 143, 0.25); |
||||||
|
.icon { |
||||||
|
width: 38rpx; |
||||||
|
height: 38rpx; |
||||||
|
} |
||||||
|
&::after { |
||||||
|
position: absolute; |
||||||
|
bottom: -8rpx; |
||||||
|
left: 50%; |
||||||
|
transform: translateX(-50%); |
||||||
|
display: block; |
||||||
|
content: ''; |
||||||
|
width: 0; |
||||||
|
height: 0; |
||||||
|
border-style: solid; |
||||||
|
border-width: 10rpx 10rpx 0 10rpx; |
||||||
|
border-color: #85ccfc transparent transparent transparent; |
||||||
|
} |
||||||
|
} |
||||||
|
.line-left, |
||||||
|
.line-right { |
||||||
|
width: 43rpx; |
||||||
|
height: 12rpx; |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.center { |
||||||
|
position: relative; |
||||||
|
z-index: 1; |
||||||
|
margin: 0 -14rpx; |
||||||
|
border: 2px solid #fff; |
||||||
|
width: 40rpx; |
||||||
|
height: 40rpx; |
||||||
|
box-sizing: border-box; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
border-radius: 50%; |
||||||
|
background-color: rgba(203, 213, 225, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
line-height: 1; |
||||||
|
} |
||||||
|
.sub-center { |
||||||
|
position: relative; |
||||||
|
z-index: 1; |
||||||
|
margin: 8rpx -14rpx; |
||||||
|
width: 24rpx; |
||||||
|
height: 24rpx; |
||||||
|
box-sizing: border-box; |
||||||
|
border: 2px solid #fff; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: rgba(203, 213, 225, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.name { |
||||||
|
margin: 20rpx auto 0; |
||||||
|
writing-mode: vertical-lr; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
letter-spacing: 8rpx; |
||||||
|
} |
||||||
|
&.active { |
||||||
|
.line-left, |
||||||
|
.line-right { |
||||||
|
background-color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
.center { |
||||||
|
background-color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
.sub-center { |
||||||
|
background-color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
&.last-active { |
||||||
|
.badge { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.line-right { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.name { |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
&:first-of-type { |
||||||
|
.line-left { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
&:last-of-type { |
||||||
|
.line-right { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.footer { |
||||||
|
position: fixed; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
padding: 30rpx 30rpx calc(env(safe-area-inset-bottom) + 30rpx); |
||||||
|
background: rgba(255, 255, 255, 0.8); |
||||||
|
box-shadow: 0rpx -4rpx 21rpx 0rpx rgba(0, 0, 0, 0.07); |
||||||
|
border-radius: 0rpx 0rpx 0rpx 0rpx; |
||||||
|
.btn { |
||||||
|
height: 96rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: linear-gradient(90deg, #9ddffd 0%, #4ab8fd 100%); |
||||||
|
box-shadow: 0rpx 15rpx 30rpx -6rpx rgba(74, 172, 219, 0.4); |
||||||
|
border-radius: 16rpx 16rpx 16rpx 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
const _app = getApp<IAppOption>() |
||||||
|
const imagesUrl = _app.globalData.imageUrl |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
centerLat: 22.586521, // 纬度
|
||||||
|
centerLng: 113.930801, // 经度
|
||||||
|
markers: [ |
||||||
|
{ |
||||||
|
id: 1, |
||||||
|
latitude: 22.586521, |
||||||
|
longitude: 113.930801, |
||||||
|
iconPath: `${imagesUrl}/icon64.png`, // 蓝圈白边站点图标
|
||||||
|
width: 14, |
||||||
|
height: 14, |
||||||
|
anchor: { x: 1, y: 1 }, |
||||||
|
label: '1111111', |
||||||
|
customCallout: { |
||||||
|
display: 'ALWAYS', // 永久显示气泡,不用点击
|
||||||
|
anchorY: -4, |
||||||
|
}, |
||||||
|
collision: 'marker', |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: 2, |
||||||
|
latitude: 22.58893, |
||||||
|
longitude: 113.92762, |
||||||
|
iconPath: `${imagesUrl}/icon64.png`, // 蓝圈白边站点图标
|
||||||
|
anchor: { x: 1, y: 1 }, |
||||||
|
width: 14, |
||||||
|
height: 14, |
||||||
|
customCallout: { |
||||||
|
display: 'ALWAYS', // 永久显示气泡,不用点击
|
||||||
|
anchorY: -4, |
||||||
|
}, |
||||||
|
collision: 'marker', |
||||||
|
}, |
||||||
|
], |
||||||
|
polyline: [ |
||||||
|
{ |
||||||
|
points: [ |
||||||
|
{ latitude: 22.586521, longitude: 113.930801 }, |
||||||
|
{ latitude: 22.58893, longitude: 113.92762 }, |
||||||
|
], |
||||||
|
color: 'rgba(74, 184, 253, 1)', |
||||||
|
width: 6, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
onLoad() {}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,83 @@ |
|||||||
|
<view class="page"> |
||||||
|
<map |
||||||
|
class="map" |
||||||
|
latitude="{{centerLat}}" |
||||||
|
longitude="{{centerLng}}" |
||||||
|
scale="14" |
||||||
|
markers="{{markers}}" |
||||||
|
polyline="{{polyline}}" |
||||||
|
enable-overlooking |
||||||
|
enable-zoom |
||||||
|
enable-scroll |
||||||
|
enable-rotate |
||||||
|
> |
||||||
|
<view class="to-center"> |
||||||
|
<image class="icon" src="/images/icon2.png"></image> |
||||||
|
</view> |
||||||
|
<cover-view slot="callout"> |
||||||
|
<cover-view class="mark" marker-id="{{index+1}}" wx:for="{{2}}" wx:key="index"> |
||||||
|
<cover-view class="mark-container"> |
||||||
|
<cover-image class="icon" src="/images/icon46.png"></cover-image> |
||||||
|
<cover-view class="wrap"> |
||||||
|
<cover-view class="title">留仙洞园区</cover-view> |
||||||
|
<cover-view class="content">留仙洞园区体育馆·生公寓 距离下一站2分钟</cover-view> |
||||||
|
</cover-view> |
||||||
|
</cover-view> |
||||||
|
<cover-view class="tril"></cover-view> |
||||||
|
</cover-view> |
||||||
|
</cover-view> |
||||||
|
</map> |
||||||
|
<view class="banner"> |
||||||
|
<view class="b-header"> |
||||||
|
<image class="icon1" src="/images/icon46.png"></image> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="w-header"> |
||||||
|
<view class="name"> |
||||||
|
西丽湖园区 |
||||||
|
<image class="icon2" src="/images/icon65.png"></image> |
||||||
|
留仙洞园区 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="content">首 07:00 末 20:00</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="tip"> |
||||||
|
<image class="icon" src="/images/icon66.png"></image> |
||||||
|
<view class="content">点击当前所在站点或点击定位即可查看到站信息</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="container"> |
||||||
|
<view class="next"> |
||||||
|
<view class="n-header"> |
||||||
|
<view class="title"> |
||||||
|
预计 |
||||||
|
<view class="high">11:55</view> |
||||||
|
到站 |
||||||
|
</view> |
||||||
|
<view class="tag"> |
||||||
|
<image class="icon" src="/images/icon67.png"></image> |
||||||
|
发车时刻表 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="content">下一站:官龙山园区报告厅</view> |
||||||
|
</view> |
||||||
|
<view class="scroll"> |
||||||
|
<view class="item {{index<=4 && 'active'}} {{index==4 && 'last-active'}}" wx:for="{{10}}" wx:key="index"> |
||||||
|
<view class="top"> |
||||||
|
<view class="badge"> |
||||||
|
<image class="icon" src="/images/icon68.png"></image> |
||||||
|
</view> |
||||||
|
<view class="line-left"></view> |
||||||
|
<view wx:if="{{index==0}}" class="center">始</view> |
||||||
|
<view wx:elif="{{index==9}}" class="center">终</view> |
||||||
|
<view wx:else class="sub-center"></view> |
||||||
|
<view class="line-right"></view> |
||||||
|
</view> |
||||||
|
<view class="name">留仙洞园区体育馆</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="footer"> |
||||||
|
<view class="btn">校园地图</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "智能助手", |
||||||
|
"usingComponents": { |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,160 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.page-back { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(0, 0, 0, 0.9); |
||||||
|
} |
||||||
|
|
||||||
|
.chat-page { |
||||||
|
width: 100%; |
||||||
|
height: 100vh; |
||||||
|
box-sizing: border-box; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
/* ========== 聊天消息列表 ========== */ |
||||||
|
.chat-scroll { |
||||||
|
flex: 1; |
||||||
|
position: relative; |
||||||
|
padding: 160rpx 28rpx 0; |
||||||
|
box-sizing: border-box; |
||||||
|
box-sizing: border-box; |
||||||
|
|
||||||
|
.history { |
||||||
|
width: 167rpx; |
||||||
|
height: 56rpx; |
||||||
|
background: linear-gradient(90deg, #9ddffd 0%, #4ab8fd 100%); |
||||||
|
font-size: 28rpx; |
||||||
|
color: #fff; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
border-radius: 54rpx; |
||||||
|
} |
||||||
|
.first-card { |
||||||
|
margin-top: 39rpx; |
||||||
|
padding: 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(31, 41, 55, 1); |
||||||
|
border-radius: 24rpx; |
||||||
|
line-height: 56rpx; |
||||||
|
background: linear-gradient(180deg, rgba(241, 249, 255, 0.84) 0%, #ffffff 100%); |
||||||
|
border: 1px solid rgba(255, 255, 255, 1); |
||||||
|
backdrop-filter: blur(38px); |
||||||
|
box-shadow: 0px 15px 30px -4px rgba(0, 96, 143, 0.09); |
||||||
|
.high { |
||||||
|
display: inline; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.tip { |
||||||
|
margin-top: 42rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(203, 213, 225, 1); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.chat-messages { |
||||||
|
clear: both; |
||||||
|
display: flow-root; |
||||||
|
.user-message { |
||||||
|
clear: both; |
||||||
|
float: right; |
||||||
|
margin-left: 80rpx; |
||||||
|
margin-top: 42rpx; |
||||||
|
padding: 24rpx 32rpx; |
||||||
|
background: rgba(74, 184, 253, 1); |
||||||
|
border-radius: 24rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: #fff; |
||||||
|
line-height: 56rpx; |
||||||
|
} |
||||||
|
.ai-message { |
||||||
|
clear: both; |
||||||
|
float: left; |
||||||
|
margin-right: 80rpx; |
||||||
|
margin-top: 42rpx; |
||||||
|
padding: 24rpx 32rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
line-height: 56rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
.high { |
||||||
|
display: inline; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.scroll-bottom-safe { |
||||||
|
clear: both; |
||||||
|
height: 223rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.chat-input { |
||||||
|
position: fixed; |
||||||
|
left: 0; |
||||||
|
width: calc(100% - 120rpx); |
||||||
|
bottom: 83rpx; |
||||||
|
margin: 0 30rpx; |
||||||
|
padding: 0 18rpx; |
||||||
|
height: 96rpx; |
||||||
|
border-radius: 95rpx; |
||||||
|
background-color: #fff; |
||||||
|
box-shadow: 0px 15px 30px 0px rgba(0, 96, 143, 0.09); |
||||||
|
.freetext { |
||||||
|
padding-left: 26rpx; |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
.input { |
||||||
|
flex: 1; |
||||||
|
} |
||||||
|
.icon { |
||||||
|
padding-left: 26rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 60rpx; |
||||||
|
height: 60rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.voice { |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
.content { |
||||||
|
flex: 1; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.icon { |
||||||
|
padding-left: 26rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 60rpx; |
||||||
|
height: 60rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.voiceing { |
||||||
|
padding: 213rpx 0 0; |
||||||
|
position: fixed; |
||||||
|
left: 0; |
||||||
|
bottom: 0; |
||||||
|
width: 100%; |
||||||
|
height: 448rpx; |
||||||
|
box-sizing: border-box; |
||||||
|
.tip { |
||||||
|
font-size: 32rpx; |
||||||
|
color: #fff; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.ani { |
||||||
|
display: block; |
||||||
|
margin: 23rpx auto 0; |
||||||
|
width: 400rpx; |
||||||
|
height: 86rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,251 @@ |
|||||||
|
/** |
||||||
|
* Chat 聊天页面 |
||||||
|
*/ |
||||||
|
|
||||||
|
interface Message { |
||||||
|
id: string |
||||||
|
role: 'user' | 'ai' |
||||||
|
content: string |
||||||
|
timestamp: number |
||||||
|
} |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: { |
||||||
|
/** 消息列表 */ |
||||||
|
messages: [] as Message[], |
||||||
|
/** 输入文本 */ |
||||||
|
inputText: '', |
||||||
|
/** 输入模式:text 或 voice */ |
||||||
|
inputMode: 'voice' as 'text' | 'voice', |
||||||
|
/** 是否正在录音 */ |
||||||
|
isRecording: false, |
||||||
|
/** 录音提示文本 */ |
||||||
|
recordingTip: '松手发送,上移取消', |
||||||
|
/** 录音管理器 */ |
||||||
|
recorderManager: null as WechatMiniprogram.RecorderManager | null, |
||||||
|
/** 滚动到指定消息 */ |
||||||
|
scrollToView: '', |
||||||
|
/** 页面顶部距离 */ |
||||||
|
pageTop: 0, |
||||||
|
/** 导航栏背景 */ |
||||||
|
background: 'transparent', |
||||||
|
}, |
||||||
|
|
||||||
|
onLoad() { |
||||||
|
// 初始化录音管理器
|
||||||
|
const recorderManager = wx.getRecorderManager() |
||||||
|
recorderManager.onStart(() => { |
||||||
|
this.setData({ isRecording: true, recordingTip: '松手发送,上移取消' }) |
||||||
|
}) |
||||||
|
recorderManager.onStop((res) => { |
||||||
|
this.setData({ isRecording: false }) |
||||||
|
if (res.duration < 1000) { |
||||||
|
wx.showToast({ title: '录音时间太短', icon: 'none' }) |
||||||
|
return |
||||||
|
} |
||||||
|
// 发送语音消息
|
||||||
|
this.sendVoiceMessage(res.tempFilePath, res.duration) |
||||||
|
}) |
||||||
|
recorderManager.onError((err) => { |
||||||
|
this.setData({ isRecording: false }) |
||||||
|
wx.showToast({ title: '录音失败', icon: 'none' }) |
||||||
|
console.error('录音错误:', err) |
||||||
|
}) |
||||||
|
this.setData({ recorderManager }) |
||||||
|
|
||||||
|
// 获取系统信息设置页面顶部距离
|
||||||
|
const systemInfo = wx.getSystemInfoSync() |
||||||
|
this.setData({ pageTop: systemInfo.statusBarHeight + 44 }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 切换到语音模式 |
||||||
|
*/ |
||||||
|
switchToVoice() { |
||||||
|
// 如果 input 有内容,不允许切换到语音模式
|
||||||
|
if (this.data.inputText.trim()) { |
||||||
|
wx.showToast({ title: '请先发送或清空内容', icon: 'none' }) |
||||||
|
return |
||||||
|
} |
||||||
|
this.setData({ inputMode: 'voice' }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 切换到文本模式 |
||||||
|
*/ |
||||||
|
switchToText() { |
||||||
|
this.setData({ inputMode: 'text' }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 输入框内容变化 |
||||||
|
*/ |
||||||
|
onInputChange(e: WechatMiniprogram.InputEvent) { |
||||||
|
this.setData({ inputText: e.detail.value }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 发送文本消息 |
||||||
|
*/ |
||||||
|
onSendText() { |
||||||
|
const { inputText } = this.data |
||||||
|
if (!inputText.trim()) return |
||||||
|
|
||||||
|
// 添加用户消息
|
||||||
|
const userMessage: Message = { |
||||||
|
id: `user_${Date.now()}`, |
||||||
|
role: 'user', |
||||||
|
content: inputText.trim(), |
||||||
|
timestamp: Date.now(), |
||||||
|
} |
||||||
|
this.setData({ |
||||||
|
messages: [...this.data.messages, userMessage], |
||||||
|
inputText: '', |
||||||
|
}) |
||||||
|
|
||||||
|
// 模拟 AI 回复
|
||||||
|
this.simulateAIResponse() |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* chat-input 区域长按开始录音 |
||||||
|
*/ |
||||||
|
onChatInputLongPress() { |
||||||
|
// 文本模式下有内容时,不允许录音
|
||||||
|
if (this.data.inputMode === 'text' && this.data.inputText.trim()) { |
||||||
|
wx.showToast({ title: '请先发送或清空内容', icon: 'none' }) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const { recorderManager } = this.data |
||||||
|
if (!recorderManager) return |
||||||
|
|
||||||
|
recorderManager.start({ |
||||||
|
duration: 60000, |
||||||
|
sampleRate: 16000, |
||||||
|
numberOfChannels: 1, |
||||||
|
encodeBitRate: 48000, |
||||||
|
format: 'mp3', |
||||||
|
}) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* chat-input 区域触摸开始 |
||||||
|
*/ |
||||||
|
onChatInputStart() { |
||||||
|
// 用于检测长按,不做实际操作
|
||||||
|
// 长按逻辑由 bindlongpress 处理
|
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* chat-input 区域触摸结束 |
||||||
|
*/ |
||||||
|
onChatInputEnd() { |
||||||
|
const { recorderManager, isRecording } = this.data |
||||||
|
if (!recorderManager || !isRecording) return |
||||||
|
recorderManager.stop() |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* chat-input 区域触摸取消 |
||||||
|
*/ |
||||||
|
onChatInputCancel() { |
||||||
|
const { recorderManager, isRecording } = this.data |
||||||
|
if (!recorderManager || !isRecording) return |
||||||
|
recorderManager.stop() |
||||||
|
this.setData({ isRecording: false, recordingTip: '录音已取消' }) |
||||||
|
wx.showToast({ title: '录音已取消', icon: 'none' }) |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* voiceing 区域触摸移动(检测手指是否移出) |
||||||
|
*/ |
||||||
|
onVoiceingTouchMove(e: WechatMiniprogram.TouchEvent) { |
||||||
|
const { isRecording, recordingTip } = this.data |
||||||
|
if (!isRecording) return |
||||||
|
|
||||||
|
// 获取触摸点位置
|
||||||
|
const touch = e.touches[0] |
||||||
|
const systemInfo = wx.getSystemInfoSync() |
||||||
|
const screenHeight = systemInfo.screenHeight |
||||||
|
|
||||||
|
// voiceing 区域高度为 448rpx,约 224px
|
||||||
|
// 当手指 Y 坐标小于 (屏幕高度 - voiceing高度) 时,说明手指移出了区域
|
||||||
|
const voiceingHeight = 224 // 448rpx ≈ 224px
|
||||||
|
const threshold = screenHeight - voiceingHeight |
||||||
|
|
||||||
|
if (touch.clientY < threshold) { |
||||||
|
// 手指移出区域,准备取消
|
||||||
|
if (recordingTip !== '上移取消录音') { |
||||||
|
this.setData({ recordingTip: '上移取消录音' }) |
||||||
|
} |
||||||
|
} else { |
||||||
|
// 手指在区域内,恢复提示
|
||||||
|
if (recordingTip !== '松手发送,上移取消') { |
||||||
|
this.setData({ recordingTip: '松手发送,上移取消' }) |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* voiceing 区域触摸结束 |
||||||
|
*/ |
||||||
|
onVoiceingTouchEnd(e: WechatMiniprogram.TouchEvent) { |
||||||
|
const { recorderManager, isRecording, recordingTip } = this.data |
||||||
|
if (!recorderManager || !isRecording) return |
||||||
|
|
||||||
|
// 获取触摸点位置
|
||||||
|
const changedTouch = e.changedTouches[0] |
||||||
|
const systemInfo = wx.getSystemInfoSync() |
||||||
|
const screenHeight = systemInfo.screenHeight |
||||||
|
const voiceingHeight = 224 // 448rpx ≈ 224px
|
||||||
|
const threshold = screenHeight - voiceingHeight |
||||||
|
|
||||||
|
// 如果手指移出区域,取消录音
|
||||||
|
if (changedTouch.clientY < threshold || recordingTip === '上移取消录音') { |
||||||
|
recorderManager.stop() |
||||||
|
this.setData({ isRecording: false, recordingTip: '录音已取消' }) |
||||||
|
wx.showToast({ title: '录音已取消', icon: 'none' }) |
||||||
|
} else { |
||||||
|
// 手指在区域内,正常结束录音并发送
|
||||||
|
recorderManager.stop() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 发送语音消息 |
||||||
|
*/ |
||||||
|
sendVoiceMessage(filePath: string, duration: number) { |
||||||
|
const userMessage: Message = { |
||||||
|
id: `user_${Date.now()}`, |
||||||
|
role: 'user', |
||||||
|
content: `[语音 ${Math.ceil(duration / 1000)}秒]`, |
||||||
|
timestamp: Date.now(), |
||||||
|
} |
||||||
|
this.setData({ |
||||||
|
messages: [...this.data.messages, userMessage], |
||||||
|
}) |
||||||
|
|
||||||
|
// 模拟 AI 回复
|
||||||
|
this.simulateAIResponse() |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* 模拟 AI 回复 |
||||||
|
*/ |
||||||
|
simulateAIResponse() { |
||||||
|
setTimeout(() => { |
||||||
|
const aiMessage: Message = { |
||||||
|
id: `ai_${Date.now()}`, |
||||||
|
role: 'ai', |
||||||
|
content: '好的,我已经收到您的消息,正在为您处理中...', |
||||||
|
timestamp: Date.now(), |
||||||
|
} |
||||||
|
this.setData({ |
||||||
|
messages: [...this.data.messages, aiMessage], |
||||||
|
}) |
||||||
|
}, 1000) |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
<view |
||||||
|
class="chat-page" |
||||||
|
style="background: url('/images/bg3.png') no-repeat top center/100% 556rpx;padding-top: {{pageTop}}px;" |
||||||
|
> |
||||||
|
<navbar fixed customStyle="background:{{background}};"> |
||||||
|
<van-icon class="page-back" name="arrow-left" slot="left" /> |
||||||
|
</navbar> |
||||||
|
<scroll-view class="chat-scroll" scroll-y scroll-into-view="{{scrollToView}}" enhanced show-scrollbar="{{false}}"> |
||||||
|
<view class="history">最近对话</view> |
||||||
|
<view class="first-card"> |
||||||
|
你好呀~我是你的活动创建小助手你可以直接告诉我想创建什么活动,比如 |
||||||
|
<view class="high">“下周五下午3点在报告厅办一场心理健康讲座”</view> |
||||||
|
我会帮你自动整理活动信息。 |
||||||
|
</view> |
||||||
|
<view class="tip">特别说明:内容均为ai生成,仅供参考</view> |
||||||
|
<view class="chat-messages"> |
||||||
|
<block wx:for="{{4}}" wx:key="index"> |
||||||
|
<view class="user-message">请帮我创建一个下周活动,活动主题是新生音乐会,活动地点是学校音乐厅</view> |
||||||
|
<view class="ai-message"> |
||||||
|
信息我已经整理好~我还需要再确认一点信息: |
||||||
|
<view class="high">您的活动打算什么时候开始呢?告诉我开始和结束的时间,我来帮您创建~</view> |
||||||
|
</view> |
||||||
|
</block> |
||||||
|
</view> |
||||||
|
<view class="scroll-bottom-safe"></view> |
||||||
|
</scroll-view> |
||||||
|
<view |
||||||
|
class="chat-input" |
||||||
|
bindtouchstart="onChatInputStart" |
||||||
|
bindtouchend="onChatInputEnd" |
||||||
|
bindtouchcancel="onChatInputCancel" |
||||||
|
bindlongpress="onChatInputLongPress" |
||||||
|
> |
||||||
|
<!-- 文本输入模式 --> |
||||||
|
<view class="freetext" wx:if="{{inputMode === 'text'}}"> |
||||||
|
<input |
||||||
|
class="input" |
||||||
|
placeholder-class="input-place" |
||||||
|
type="text" |
||||||
|
placeholder="发消息或按住说话..." |
||||||
|
value="{{inputText}}" |
||||||
|
disabled="{{isRecording}}" |
||||||
|
bindinput="onInputChange" |
||||||
|
bindconfirm="onSendText" |
||||||
|
/> |
||||||
|
<!-- 切换到语音模式按钮 --> |
||||||
|
<image class="icon" src="/images/icon44.png" bindtap="switchToVoice" catchtap></image> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 语音输入模式(未录音状态) --> |
||||||
|
<view class="voice" wx:if="{{inputMode === 'voice' && !isRecording}}"> |
||||||
|
<view class="content">按住说话</view> |
||||||
|
<!-- 切换到文本模式按钮 --> |
||||||
|
<image class="icon" src="/images/icon45.png" bindtap="switchToText" catchtap></image> |
||||||
|
</view> |
||||||
|
|
||||||
|
<!-- 录音进行中状态 --> |
||||||
|
<view |
||||||
|
class="voiceing" |
||||||
|
wx:if="{{isRecording}}" |
||||||
|
style="background: url('/images/bg4.png') no-repeat top center/100%" |
||||||
|
bindtouchmove="onVoiceingTouchMove" |
||||||
|
bindtouchend="onVoiceingTouchEnd" |
||||||
|
> |
||||||
|
<view class="tip">{{recordingTip}}</view> |
||||||
|
<image class="ani" src="/images/gif1.gif"></image> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "最近对话", |
||||||
|
"navigationStyle": "default", |
||||||
|
"usingComponents": {} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
.page { |
||||||
|
padding: 30rpx; |
||||||
|
.card { |
||||||
|
padding: 32rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
.wrap { |
||||||
|
.title { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
line-height: 56rpx; |
||||||
|
} |
||||||
|
.date{ |
||||||
|
margin-top: 14rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.status{ |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(203, 213, 225, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
const _app = getApp<IAppOption>(); |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
}); |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<view class="page"> |
||||||
|
<view class="card"> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="title">新生音乐会</view> |
||||||
|
<view class="date">2026/6/15</view> |
||||||
|
</view> |
||||||
|
<view class="status">草稿</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
@ -1,3 +1,78 @@ |
|||||||
.page { |
.page { |
||||||
padding: 20rpx; |
padding: 0 30rpx; |
||||||
|
.page-title { |
||||||
|
padding-top: 70rpx; |
||||||
|
font-size: 64rpx; |
||||||
|
line-height: 72rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
font-weight: bold; |
||||||
|
.t1 { |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
} |
||||||
|
.t2 { |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.page-body { |
||||||
|
margin-top: 74rpx; |
||||||
|
padding: 30rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: #fff; |
||||||
|
.tip { |
||||||
|
padding: 24rpx; |
||||||
|
background-color: rgba(254, 181, 74, 0.1); |
||||||
|
border-radius: 16rpx; |
||||||
|
display: flex; |
||||||
|
gap: 18rpx; |
||||||
|
.icon { |
||||||
|
flex-shrink: 0; |
||||||
|
margin-top: 8rpx; |
||||||
|
width: 32rpx; |
||||||
|
height: 32rpx; |
||||||
|
} |
||||||
|
.content { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.form-item { |
||||||
|
margin-top: 32rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 32rpx; |
||||||
|
padding: 0 30rpx; |
||||||
|
background-color: rgba(248, 250, 253, 1); |
||||||
|
border-radius: 16rpx; |
||||||
|
.label { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
&.required::before { |
||||||
|
display: inline; |
||||||
|
content: '*'; |
||||||
|
color: rgba(253, 91, 89, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.input { |
||||||
|
height: 84rpx; |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
} |
||||||
|
.input-place { |
||||||
|
color: rgba(203, 213, 225, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.tip2{ |
||||||
|
margin-top: 28rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
line-height: 42rpx; |
||||||
|
} |
||||||
|
.agreement{ |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(71, 85, 105, 1); |
||||||
|
.high{ |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,3 +1,30 @@ |
|||||||
<view class="page"> |
<view |
||||||
<text>登录页</text> |
class="page" |
||||||
|
style="background: url('/images/bg6.png') no-repeat top center/100% 556rpx;padding-top: {{pageTop}}px;" |
||||||
|
> |
||||||
|
<view class="page-title"> |
||||||
|
<view class="t1">绑定</view> |
||||||
|
<view class="t2">深职大账号</view> |
||||||
|
</view> |
||||||
|
<view class="page-body"> |
||||||
|
<view class="tip"> |
||||||
|
<image class="icon" src="/images/icon26.png"></image> |
||||||
|
<view class="content"> |
||||||
|
请确保您输入的密码是正确的,错误次数超过2次SIC将冻结您的账号一小时。超过一定错误次数,学校系统将冻结您的账号。忘记密码请前往 |
||||||
|
https://authserver.szpu.edu.cn进行重置 |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="form-item"> |
||||||
|
<view class="label required">账号</view> |
||||||
|
<input class="input" placeholder-class="input-palce" type="number" placeholder="请输入学工号" /> |
||||||
|
</view> |
||||||
|
<view class="form-item"> |
||||||
|
<view class="label required">密码</view> |
||||||
|
<input class="input" placeholder-class="input-palce" type="text" placeholder="请输入密码" /> |
||||||
|
</view> |
||||||
|
<view class="tip2">SIC+仅将您的密码用于单次身份验证,不会保存您的密码</view> |
||||||
|
<view class="agreement"> |
||||||
|
<radio>我已阅读并接受《深职大SIC+小程序隐私保护指引》</radio> |
||||||
|
</view> |
||||||
|
</view> |
||||||
</view> |
</view> |
||||||
|
|||||||
@ -1,3 +1,7 @@ |
|||||||
{ |
{ |
||||||
"usingComponents": {} |
"navigationStyle": "custom", |
||||||
|
"navigationBarTitleText": "我的", |
||||||
|
"usingComponents": { |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,3 +1,96 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
.page { |
.page { |
||||||
padding: 20rpx; |
padding: 0 32rpx; |
||||||
|
.user { |
||||||
|
padding-top: 50rpx; |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
.avatar { |
||||||
|
width: 116rpx; |
||||||
|
height: 116rpx; |
||||||
|
border-radius: 50%; |
||||||
|
border: 1px solid #fff; |
||||||
|
.a-img { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
padding-top: 10rpx; |
||||||
|
padding-left: 24rpx; |
||||||
|
.w-header { |
||||||
|
display: flex; |
||||||
|
align-items: baseline; |
||||||
|
gap: 20rpx; |
||||||
|
.name { |
||||||
|
font-size: 36rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.id { |
||||||
|
font-size: 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 16rpx; |
||||||
|
font-size: 26rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.kkd { |
||||||
|
margin-top: 42rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
gap: 22rpx; |
||||||
|
.k-item { |
||||||
|
flex: 1; |
||||||
|
background: linear-gradient(180deg, #ebfdff 0%, #ffffff 100%); |
||||||
|
border-radius: 24rpx 24rpx 24rpx 24rpx; |
||||||
|
border: 2rpx solid #ffffff; |
||||||
|
.wrap { |
||||||
|
padding: 32rpx; |
||||||
|
.title { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 16rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.list { |
||||||
|
margin-top: 30rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
padding: 0 32rpx; |
||||||
|
.list-item { |
||||||
|
padding: 32rpx 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
gap: 18rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(203, 213, 225, 1); |
||||||
|
border-bottom: 1px solid rgba(203, 213, 225, 0.21); |
||||||
|
.icon { |
||||||
|
flex-shrink: 0; |
||||||
|
width: 42rpx; |
||||||
|
height: 42rpx; |
||||||
|
} |
||||||
|
.name { |
||||||
|
flex: 1; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,3 +1,48 @@ |
|||||||
<view class="page"> |
<view |
||||||
<text>我的</text> |
class="page" |
||||||
|
style="background: url('/images/bg5.png') no-repeat top center/100% 655rpx;padding-top: {{pageTop}}px;" |
||||||
|
> |
||||||
|
<view class="user"> |
||||||
|
<view class="avatar"> |
||||||
|
<image class="a-img" src="/images/icon2.png"></image> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="w-header"> |
||||||
|
<view class="name">奇妙的双子座</view> |
||||||
|
<view class="id">20989182</view> |
||||||
|
</view> |
||||||
|
<view class="content">计算机科学学院软件工程3班</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="kkd"> |
||||||
|
<view class="k-item" bind:tap="handleMyAct"> |
||||||
|
<view class="wrap" style="background: url('/images/icon58.png') no-repeat top 24rpx right 24rpx/84rpx 84rpx"> |
||||||
|
<view class="title">我的活动</view> |
||||||
|
<view class="content">已参与10个活动</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="k-item" bind:tap="handleMyAgent"> |
||||||
|
<view class="wrap" style="background: url('/images/icon59.png') no-repeat top 24rpx right 24rpx/84rpx 84rpx"> |
||||||
|
<view class="title">我的智能体</view> |
||||||
|
<view class="content">已启用20个智能体</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="list"> |
||||||
|
<view class="list-item" bind:tap="handleMyCommet"> |
||||||
|
<image class="icon" src="/images/icon59.png"></image> |
||||||
|
<view class="name">我的评论</view> |
||||||
|
<van-icon name="arrow" /> |
||||||
|
</view> |
||||||
|
<view class="list-item"> |
||||||
|
<image class="icon" src="/images/icon60.png"></image> |
||||||
|
<view class="name">我的收藏</view> |
||||||
|
<van-icon name="arrow" /> |
||||||
|
</view> |
||||||
|
<view class="list-item"> |
||||||
|
<image class="icon" src="/images/icon61.png"></image> |
||||||
|
<view class="name">我的评论</view> |
||||||
|
<van-icon name="arrow" /> |
||||||
|
</view> |
||||||
|
</view> |
||||||
</view> |
</view> |
||||||
|
|||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "我的活动", |
||||||
|
"navigationStyle": "default", |
||||||
|
"usingComponents": { |
||||||
|
"van-tab": "@vant/weapp/tab/index", |
||||||
|
"van-tabs": "@vant/weapp/tabs/index", |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,141 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
.tabs { |
||||||
|
.van-tabs__line { |
||||||
|
width: 42rpx !important; |
||||||
|
} |
||||||
|
.van-tab--active { |
||||||
|
--tab-font-size: 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.page0 { |
||||||
|
padding: 43rpx 30rpx; |
||||||
|
.card { |
||||||
|
margin-bottom: 24rpx; |
||||||
|
padding: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
.c-body { |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
padding-bottom: 24rpx; |
||||||
|
border-bottom: 1px solid rgba(247, 248, 250, 1); |
||||||
|
&:last-of-type { |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.photo { |
||||||
|
flex-shrink: 0; |
||||||
|
position: relative; |
||||||
|
width: 262rpx; |
||||||
|
height: 196rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
overflow: hidden; |
||||||
|
.status { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
padding: 8rpx 16rpx; |
||||||
|
font-size: 22rpx; |
||||||
|
border-radius: 16rpx 0rpx 16rpx 0rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
&.status1 { |
||||||
|
background: #feb54a; |
||||||
|
} |
||||||
|
&.status2 { |
||||||
|
background: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
&.status3 { |
||||||
|
background: rgba(111, 220, 174, 1); |
||||||
|
} |
||||||
|
&.status4 { |
||||||
|
background: rgba(203, 213, 225, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.p-img { |
||||||
|
border-radius: 16rpx; |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
.user { |
||||||
|
position: absolute; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
box-sizing: border-box; |
||||||
|
text-align: center; |
||||||
|
line-height: 44rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(255, 255, 255, 1); |
||||||
|
background-color: rgba(0, 0, 0, 0.36); |
||||||
|
backdrop-filter: blur(8rpx); |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
.title { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
line-height: 48rpx; |
||||||
|
font-weight: bold; |
||||||
|
height: 96rpx; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
display: -webkit-box; |
||||||
|
-webkit-box-orient: vertical; |
||||||
|
-webkit-line-clamp: 2; |
||||||
|
} |
||||||
|
.date, |
||||||
|
.site { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(100, 116, 139, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8rpx; |
||||||
|
.icon { |
||||||
|
width: 22rpx; |
||||||
|
height: 22rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.c-footer { |
||||||
|
padding-top: 24rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: flex-end; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 16rpx; |
||||||
|
.stat { |
||||||
|
flex-grow: 1; |
||||||
|
flex-shrink: 0; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
.btn1 { |
||||||
|
padding: 12rpx 32rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 50rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
.btn2 { |
||||||
|
padding: 12rpx 32rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 50rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: #fff; |
||||||
|
background-color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
const _app = getApp<IAppOption>(); |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
}); |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
<view class="page"> |
||||||
|
<van-tabs |
||||||
|
class="tabs" |
||||||
|
color="rgba(74, 184, 253, 1)" |
||||||
|
title-inactive-color="rgba(71, 85, 105, 1)" |
||||||
|
title-active-color="rgba(74, 184, 253, 1)" |
||||||
|
active="{{ active }}" |
||||||
|
bind:change="onChange" |
||||||
|
> |
||||||
|
<van-tab title="我发布的"> |
||||||
|
<view class="page0"> |
||||||
|
<view class="card" wx:for="{{10}}" wx:key="index"> |
||||||
|
<view class="c-body"> |
||||||
|
<view class="photo"> |
||||||
|
<view class="status status1">进行中</view> |
||||||
|
<view class="status status2">已发布</view> |
||||||
|
<view class="status status3">报名中</view> |
||||||
|
<view class="status status4">草稿</view> |
||||||
|
<view class="status status4">已取消</view> |
||||||
|
<view class="status status4">已结束</view> |
||||||
|
<image class="p-img" src="/images/bg1.png"></image> |
||||||
|
<view class="user">128人已报名</view> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="title">深职大第十五届校园歌手大赛</view> |
||||||
|
<view class="date"> |
||||||
|
<image class="icon" src="/images/icon3.png"></image> |
||||||
|
<view class="content">2026.04.01-2026.05.30</view> |
||||||
|
</view> |
||||||
|
<view class="site"> |
||||||
|
<image class="icon" src="/images/icon15.png"></image> |
||||||
|
<view class="content">留仙洞校区音乐厅</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="c-footer"> |
||||||
|
<view class="stat">86人已报名</view> |
||||||
|
<view class="btn1">删除</view> |
||||||
|
<!-- <view class="btn1">签到二维码</view> --> |
||||||
|
<!-- <view class="btn1">取消</view> --> |
||||||
|
<!-- <view class="btn1">详情</view> --> |
||||||
|
<view class="btn2">编辑</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</van-tab> |
||||||
|
<van-tab title="我参与的">内容 2</van-tab> |
||||||
|
</van-tabs> |
||||||
|
</view> |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "我的智能体", |
||||||
|
"navigationStyle": "default", |
||||||
|
"usingComponents": { |
||||||
|
"van-tab": "@vant/weapp/tab/index", |
||||||
|
"van-tabs": "@vant/weapp/tabs/index", |
||||||
|
"van-icon": "@vant/weapp/icon/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
.tabs { |
||||||
|
.van-tabs__line { |
||||||
|
width: 42rpx !important; |
||||||
|
} |
||||||
|
.van-tab--active { |
||||||
|
--tab-font-size: 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.page0 { |
||||||
|
padding: 43rpx 30rpx; |
||||||
|
.card { |
||||||
|
margin-bottom: 24rpx; |
||||||
|
padding: 24rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
display: flex; |
||||||
|
gap: 24rpx; |
||||||
|
padding-bottom: 24rpx; |
||||||
|
border-bottom: 1px solid rgba(247, 248, 250, 1); |
||||||
|
&:last-of-type { |
||||||
|
border: none; |
||||||
|
} |
||||||
|
.photo { |
||||||
|
flex-shrink: 0; |
||||||
|
position: relative; |
||||||
|
width: 158rpx; |
||||||
|
height: 158rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
overflow: hidden; |
||||||
|
.p-img { |
||||||
|
border-radius: 16rpx; |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
flex: 1; |
||||||
|
.title { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
line-height: 48rpx; |
||||||
|
font-weight: bold; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
display: -webkit-box; |
||||||
|
-webkit-box-orient: vertical; |
||||||
|
-webkit-line-clamp: 2; |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 12rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
line-height: 36rpx; |
||||||
|
} |
||||||
|
.w-footer { |
||||||
|
padding-top: 20rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: flex-end; |
||||||
|
flex-wrap: wrap; |
||||||
|
gap: 16rpx; |
||||||
|
.stat { |
||||||
|
flex-grow: 1; |
||||||
|
flex-shrink: 0; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 0.5); |
||||||
|
} |
||||||
|
.btn1 { |
||||||
|
padding: 8rpx 32rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 50rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
.btn2 { |
||||||
|
padding: 8rpx 32rpx; |
||||||
|
border: 1px solid rgba(74, 184, 253, 1); |
||||||
|
border-radius: 50rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: #fff; |
||||||
|
background-color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
const _app = getApp<IAppOption>(); |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
}); |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
<view class="page"> |
||||||
|
<van-tabs |
||||||
|
class="tabs" |
||||||
|
color="rgba(74, 184, 253, 1)" |
||||||
|
title-inactive-color="rgba(71, 85, 105, 1)" |
||||||
|
title-active-color="rgba(74, 184, 253, 1)" |
||||||
|
active="{{ active }}" |
||||||
|
bind:change="onChange" |
||||||
|
> |
||||||
|
<van-tab title="我发布的"> |
||||||
|
<view class="page0"> |
||||||
|
<view class="card" wx:for="{{10}}" wx:key="index"> |
||||||
|
<view class="photo"> |
||||||
|
<image class="p-img" src="/images/bg1.png"></image> |
||||||
|
</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="title">深职大第十五届校园歌手大赛</view> |
||||||
|
<div class="content">智能规划课表,轻松掌握学习节奏</div> |
||||||
|
<view class="w-footer"> |
||||||
|
<view class="stat">86人已报名</view> |
||||||
|
<view class="btn1">取消收藏</view> |
||||||
|
<view class="btn2">使用</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</van-tab> |
||||||
|
<van-tab title="历史使用">内容 2</van-tab> |
||||||
|
<van-tab title="我收藏的">内容 2</van-tab> |
||||||
|
</van-tabs> |
||||||
|
</view> |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"navigationBarTitleText": "我的评论", |
||||||
|
"navigationStyle": "default", |
||||||
|
"usingComponents": { |
||||||
|
"van-tab": "@vant/weapp/tab/index", |
||||||
|
"van-tabs": "@vant/weapp/tabs/index", |
||||||
|
"van-icon": "@vant/weapp/icon/index", |
||||||
|
"van-rate": "@vant/weapp/rate/index" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,167 @@ |
|||||||
|
page { |
||||||
|
background-color: rgba(247, 248, 250, 1); |
||||||
|
} |
||||||
|
|
||||||
|
.page { |
||||||
|
.tabs { |
||||||
|
.van-tabs__line { |
||||||
|
width: 42rpx !important; |
||||||
|
} |
||||||
|
.van-tab--active { |
||||||
|
--tab-font-size: 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
.page0 { |
||||||
|
padding: 43rpx 30rpx; |
||||||
|
.card { |
||||||
|
padding: 32rpx; |
||||||
|
background-color: #fff; |
||||||
|
border-radius: 24rpx; |
||||||
|
.c-header { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.status { |
||||||
|
padding: 8rpx 16rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
border-radius: 8rpx; |
||||||
|
&.status1 { |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
background-color: rgba(74, 184, 253, 0.1); |
||||||
|
} |
||||||
|
&.status2 { |
||||||
|
color: rgba(111, 220, 174, 1); |
||||||
|
background-color: rgba(111, 220, 174, 0.1); |
||||||
|
} |
||||||
|
&.status3 { |
||||||
|
color: rgba(253, 91, 89, 1); |
||||||
|
background-color: rgba(253, 91, 89, 0.1); |
||||||
|
} |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.name { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
.options { |
||||||
|
.icon { |
||||||
|
width: 32rpx; |
||||||
|
height: 32rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.station { |
||||||
|
margin-top: 28rpx; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
.date { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
.rate { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 8rpx; |
||||||
|
.num { |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(254, 181, 74, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 32rpx; |
||||||
|
font-size: 28rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
line-height: 46rpx; |
||||||
|
} |
||||||
|
.photo-wrap { |
||||||
|
margin-top: 24rpx; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(3, 1fr); |
||||||
|
gap: 16rpx; |
||||||
|
flex-wrap: wrap; |
||||||
|
.photo { |
||||||
|
aspect-ratio: 1 / 1; |
||||||
|
.p-img { |
||||||
|
display: block; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
border-radius: 16rpx; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.act-card { |
||||||
|
margin-top: 24rpx; |
||||||
|
padding: 16rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background-color: rgba(249, 250, 251, 1); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 24rpx; |
||||||
|
.ac-photo { |
||||||
|
flex-shrink: 0; |
||||||
|
border-radius: 8rpx; |
||||||
|
width: 80rpx; |
||||||
|
height: 80rpx; |
||||||
|
} |
||||||
|
.wrap { |
||||||
|
padding-top: 5rpx; |
||||||
|
.title { |
||||||
|
font-size: 32rpx; |
||||||
|
color: rgba(17, 24, 39, 1); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.content { |
||||||
|
margin-top: 8rpx; |
||||||
|
font-size: 24rpx; |
||||||
|
color: rgba(148, 163, 184, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.tip-card { |
||||||
|
margin-top: 24rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background: rgba(74, 184, 253, 0.05); |
||||||
|
padding: 20rpx 16rpx; |
||||||
|
display: flex; |
||||||
|
gap: 16rpx; |
||||||
|
.icon { |
||||||
|
margin-top: 5rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 22rpx; |
||||||
|
height: 22rpx; |
||||||
|
} |
||||||
|
.t-content { |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: rgba(74, 184, 253, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
.reject-card { |
||||||
|
margin-top: 24rpx; |
||||||
|
border-radius: 16rpx; |
||||||
|
background: rgba(253, 91, 89, 0.1); |
||||||
|
padding: 20rpx 16rpx; |
||||||
|
display: flex; |
||||||
|
gap: 16rpx; |
||||||
|
.icon { |
||||||
|
margin-top: 5rpx; |
||||||
|
flex-shrink: 0; |
||||||
|
width: 22rpx; |
||||||
|
height: 22rpx; |
||||||
|
} |
||||||
|
.t-content { |
||||||
|
font-size: 28rpx; |
||||||
|
line-height: 32rpx; |
||||||
|
color: rgba(253, 91, 89, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
const _app = getApp<IAppOption>(); |
||||||
|
|
||||||
|
Page({ |
||||||
|
data: {}, |
||||||
|
onLoad() {}, |
||||||
|
}); |
||||||
|
|
||||||
|
export {} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
<view class="page"> |
||||||
|
<van-tabs |
||||||
|
class="tabs" |
||||||
|
color="rgba(74, 184, 253, 1)" |
||||||
|
title-inactive-color="rgba(71, 85, 105, 1)" |
||||||
|
title-active-color="rgba(74, 184, 253, 1)" |
||||||
|
active="{{ active }}" |
||||||
|
bind:change="onChange" |
||||||
|
> |
||||||
|
<van-tab title="我发布的"> |
||||||
|
<view class="page0"> |
||||||
|
<view class="card"> |
||||||
|
<view class="c-header"> |
||||||
|
<view class="status status1">待审核</view> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="name">匿名</view> |
||||||
|
<view class="options"> |
||||||
|
<image class="icon"></image> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="station"> |
||||||
|
<view class="date">2026-01-01 13:00:09</view> |
||||||
|
<view class="rate"> |
||||||
|
<van-rate value="{{4.5}}" color="#F7B550" allow-half size="28rpx" void-color="#F7F8FA" /> |
||||||
|
<view class="num">4.5</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="content"> |
||||||
|
本次活动组织有序、流程顺畅,现场氛围良好。活 动内容丰富、安排合理,服务贴心到位,整体体验 良好。 |
||||||
|
</view> |
||||||
|
<view class="photo-wrap"> |
||||||
|
<view class="photo" wx:for="{{3}}" wx:key="index"> |
||||||
|
<image class="p-img" src="/images/bg1.png" mode="aspectFill"></image> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="act-card"> |
||||||
|
<image class="ac-photo" mode="aspectFill" src="/images/bg1.png"></image> |
||||||
|
<view class="wrap"> |
||||||
|
<view class="title">深职大第十五届校园歌手大赛</view> |
||||||
|
<view class="content">2026年5月30日 19:00</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
<view class="tip-card"> |
||||||
|
<image class="icon" src="/images/icon62.png"></image> |
||||||
|
<view class="t-content">内容已提交,正在审核中,预计1-2个工作日内完成审核。</view> |
||||||
|
</view> |
||||||
|
<view class="reject-card"> |
||||||
|
<image class="icon" src="/images/icon63.png"></image> |
||||||
|
<view class="t-content">驳回原因:内容中包含不当言论,请修改后重新提交。</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</view> |
||||||
|
</van-tab> |
||||||
|
<van-tab title="历史使用">内容 2</van-tab> |
||||||
|
<van-tab title="我收藏的">内容 2</van-tab> |
||||||
|
</van-tabs> |
||||||
|
</view> |
||||||