1. 新增校园巴士、智能体、我的活动等多个业务页面 2. 新增svg-icon、uploadFile通用组件 3. 新增数十个图标与背景图片资源 4. 优化活动创建、详情页面的上传与样式逻辑 5. 更新全局配置与自定义tabbarmaster
@ -0,0 +1,8 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,4 @@
|
||||
{ |
||||
"component": true, |
||||
"usingComponents": {} |
||||
} |
||||
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
/** |
||||
* SVG Icon 组件样式 |
||||
*/ |
||||
|
||||
.svg-icon { |
||||
display: block; |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,4 @@
|
||||
{ |
||||
"component": true, |
||||
"usingComponents": {} |
||||
} |
||||
@ -0,0 +1,272 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,5 +1,7 @@
|
||||
{ |
||||
"navigationBarTitleText": "创建活动", |
||||
"navigationStyle": "default", |
||||
"usingComponents": {} |
||||
"usingComponents": { |
||||
"upload-file": "/components/uploadFile/index" |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,9 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,13 @@
|
||||
const _app = getApp<IAppOption>() |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
handleEva() { |
||||
wx.navigateTo({ |
||||
url: '/pages/agentEva/index', |
||||
}) |
||||
}, |
||||
}) |
||||
|
||||
export {} |
||||
@ -0,0 +1,112 @@
@@ -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 @@
@@ -0,0 +1,7 @@
|
||||
{ |
||||
"navigationStyle": "default", |
||||
"navigationBarTitleText": "智能体", |
||||
"usingComponents": { |
||||
"van-rate": "@vant/weapp/rate/index" |
||||
} |
||||
} |
||||
@ -0,0 +1,219 @@
@@ -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 @@
@@ -0,0 +1,8 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
|
||||
export {} |
||||
@ -0,0 +1,77 @@
@@ -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 @@
@@ -0,0 +1,7 @@
|
||||
{ |
||||
"navigationBarTitleText": "校园巴士", |
||||
"navigationStyle": "default", |
||||
"usingComponents": { |
||||
"van-icon": "@vant/weapp/icon/index" |
||||
} |
||||
} |
||||
@ -0,0 +1,314 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,7 @@
|
||||
{ |
||||
"navigationBarTitleText": "智能助手", |
||||
"usingComponents": { |
||||
"van-icon": "@vant/weapp/icon/index" |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,160 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,5 @@
|
||||
{ |
||||
"navigationBarTitleText": "最近对话", |
||||
"navigationStyle": "default", |
||||
"usingComponents": {} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -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 @@
@@ -0,0 +1,8 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
|
||||
export {} |
||||
@ -0,0 +1,9 @@
@@ -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 @@
@@ -1,3 +1,78 @@
|
||||
.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 @@
@@ -1,3 +1,30 @@
|
||||
<view class="page"> |
||||
<text>登录页</text> |
||||
<view |
||||
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> |
||||
|
||||
@ -1,3 +1,7 @@
@@ -1,3 +1,7 @@
|
||||
{ |
||||
"usingComponents": {} |
||||
"navigationStyle": "custom", |
||||
"navigationBarTitleText": "我的", |
||||
"usingComponents": { |
||||
"van-icon": "@vant/weapp/icon/index" |
||||
} |
||||
} |
||||
|
||||
@ -1,3 +1,96 @@
@@ -1,3 +1,96 @@
|
||||
page { |
||||
background-color: rgba(247, 248, 250, 1); |
||||
} |
||||
.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 @@
@@ -1,3 +1,48 @@
|
||||
<view class="page"> |
||||
<text>我的</text> |
||||
<view |
||||
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> |
||||
|
||||
@ -0,0 +1,9 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,8 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
|
||||
export {} |
||||
@ -0,0 +1,49 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,8 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
|
||||
export {} |
||||
@ -0,0 +1,31 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -0,0 +1,8 @@
|
||||
const _app = getApp<IAppOption>(); |
||||
|
||||
Page({ |
||||
data: {}, |
||||
onLoad() {}, |
||||
}); |
||||
|
||||
export {} |
||||
@ -0,0 +1,58 @@
@@ -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> |
||||