小程序修改

This commit is contained in:
2025-12-10 17:00:54 +08:00
parent 23afb90cbe
commit fa3dbe0496
47 changed files with 5282 additions and 199 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
<template>
<div>
</div>
</template>
<script lang="ts" setup>
import { reactive } from "vue"
import type { LoginParam} from "@shared/types/auth"
import { authAPI } from "@shared/api/auth/auth"
const loginParam = reactive<LoginParam>({
loginType: "password"
})
async function login(){
// const result = await authAPI.login(loginParam)
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,6 @@
import { api } from '@/api/index'
export const authAPI = {
baseUrl: "/auth",
}

View File

@@ -0,0 +1,144 @@
# 智慧城市工单微信小程序
基于 uni-app x 开发的智慧城市工单管理微信小程序,包含智能客服对话、工单创建、查询和管理功能。
## 项目结构
```
workcase_wechat/
├── components/ # 组件目录
│ └── WorkcaseCreator.uvue # 工单创建组件
├── pages/ # 页面目录
│ ├── index/ # 主页(智能客服对话)
│ │ └── index.uvue
│ └── workcase/ # 工单相关页面
│ ├── list.uvue # 工单列表页
│ └── detail.uvue # 工单详情页
├── static/ # 静态资源
│ └── tabbar/ # 底部导航图标
├── App.uvue # 应用入口
├── main.uts # 主文件
├── pages.json # 页面配置
├── manifest.json # 应用配置
└── uni.scss # 全局样式
```
## 功能特性
### 1. 智能客服对话页 (pages/index/index.uvue)
- 🤖 智能客服对话界面
- 💬 实时消息收发
- 🎯 快捷操作按钮
- 📝 工单创建入口
- 🔄 打字指示器动画
**主要功能:**
- 智能问答对话
- 工单创建引导
- 快捷操作菜单
- 消息历史记录
### 2. 工单列表页 (pages/workcase/list.uvue)
- 📋 工单列表展示
- 🔍 状态和分类筛选
- 📊 统计信息卡片
- 📱 下拉刷新和上拉加载
- ⚡ 快捷操作按钮
**主要功能:**
- 工单状态筛选(全部、待处理、处理中、已完成、已取消)
- 分类筛选(设施报修、环境卫生、交通问题等)
- 工单统计概览
- 快捷操作(取消、确认完成、联系客服)
### 3. 工单详情页 (pages/workcase/detail.uvue)
- 📄 工单详细信息
- 🔄 处理进度展示
- 📷 图片附件查看
- 📝 处理记录时间线
- ⭐ 服务评价功能
**主要功能:**
- 完整的工单信息展示
- 实时进度跟踪
- 处理记录时间轴
- 图片预览功能
- 服务评价系统
### 4. 工单创建组件 (components/WorkcaseCreator.uvue)
- 📝 表单式工单创建
- 📷 图片上传最多3张
- 🏷️ 分类和优先级选择
- ✅ 表单验证
- 📤 模拟提交流程
**主要功能:**
- 多字段表单填写
- 图片选择和预览
- 实时字符计数
- 表单验证提示
## 页面导航
### 底部 TabBar 导航
- **智能助手**:主页聊天界面
- **我的工单**:工单列表页面
### 路由跳转
- 从聊天页面创建工单 → 弹出工单创建组件
- 从工单列表查看详情 → 跳转工单详情页
- 从详情页联系客服 → 返回聊天页面
## 样式设计
### 设计系统
- **主色调**:蓝色系 (#1976D2)
- **状态色彩**
- 待处理:橙色 (#F57C00)
- 处理中:蓝色 (#1976D2)
- 已完成:绿色 (#388E3C)
- 已取消:红色 (#D32F2F)
### 组件风格
- 圆角卡片设计
- 阴影效果
- 渐变按钮
- 动画过渡
## 数据模拟
当前使用模拟数据进行开发和演示:
- 模拟智能客服回复
- 模拟工单数据生成
- 模拟处理记录
- 模拟图片附件
## 开发说明
### 技术栈
- **框架**uni-app x
- **语言**Vue 3 + TypeScript (UTS)
- **样式**SCSS
- **平台**:微信小程序
### 注意事项
1. 图标文件需要替换为实际的 PNG 图片
2. 静态资源路径需要根据实际情况调整
3. API 接口需要替换为真实的后端服务
4. 图片上传功能需要配置真实的上传服务
### 后续开发
1. 集成真实的 AI 客服 API
2. 对接后端工单管理系统
3. 添加消息推送功能
4. 完善用户认证系统
5. 优化性能和用户体验
## 部署说明
1. 确保已安装 HBuilderX
2. 导入项目到 HBuilderX
3. 配置微信小程序开发者工具
4. 替换静态资源文件
5. 配置真实的 API 接口
6. 编译并发布到微信小程序平台

View File

@@ -0,0 +1,248 @@
.workcase-creator-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
flex-direction: column;
justify-content: flex-end;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.workcase-creator {
max-height: 85vh;
background-color: #FFFFFF;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
display: flex;
flex-direction: column;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.creator-header {
background-color: #FFFFFF;
padding: 20px 16px 16px;
border-bottom: 1px solid #F0F0F0;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.creator-header::before {
content: '';
position: absolute;
top: 8px;
left: 50%;
transform: translateX(-50%);
width: 36px;
height: 4px;
background-color: #E0E0E0;
border-radius: 2px;
}
.header-title {
color: #1F2329;
font-size: 18px;
font-weight: 600;
}
.close-btn {
width: 28px;
height: 28px;
border-radius: 14px;
background-color: #F5F5F5;
display: flex;
align-items: center;
justify-content: center;
}
.close-icon {
color: #8F959E;
font-size: 20px;
line-height: 1;
}
.creator-content {
flex: 1;
background-color: #FFFFFF;
padding: 16px;
}
.form-item {
margin-bottom: 24px;
}
.label {
display: block;
color: #333333;
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
}
.input, .textarea {
width: 100%;
padding: 12px 16px;
border: 1px solid #E0E0E0;
border-radius: 8px;
font-size: 16px;
background-color: #FAFAFA;
}
.textarea {
min-height: 100px;
resize: none;
}
.char-count {
color: #999999;
font-size: 12px;
text-align: right;
margin-top: 4px;
}
.picker {
width: 100%;
}
.picker-content {
padding: 12px 16px;
border: 1px solid #E0E0E0;
border-radius: 8px;
background-color: #FAFAFA;
display: flex;
justify-content: space-between;
align-items: center;
}
.picker-text {
color: #333333;
font-size: 16px;
}
.picker-arrow {
color: #999999;
font-size: 18px;
}
.upload-area {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.upload-item {
position: relative;
width: 80px;
height: 80px;
}
.upload-image {
width: 100%;
height: 100%;
border-radius: 8px;
}
.delete-btn {
position: absolute;
top: -6px;
right: -6px;
width: 20px;
height: 20px;
border-radius: 10px;
background-color: #FF5722;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #FFFFFF;
font-size: 14px;
line-height: 1;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #CCCCCC;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #FAFAFA;
}
.upload-plus {
color: #999999;
font-size: 24px;
line-height: 1;
}
.upload-text {
color: #999999;
font-size: 12px;
margin-top: 4px;
}
.upload-tip {
color: #999999;
font-size: 12px;
margin-top: 8px;
}
.creator-footer {
background-color: #FFFFFF;
padding: 16px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
border-top: 1px solid #F0F0F0;
display: flex;
gap: 12px;
}
.cancel-btn, .submit-btn {
flex: 1;
height: 44px;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
border: none;
}
.cancel-btn {
background-color: #F5F5F5;
color: #666666;
}
.submit-btn {
background-color: #5B8FF9;
color: #FFFFFF;
}
.submit-btn[disabled] {
background-color: #CCCCCC;
color: #999999;
}

View File

@@ -0,0 +1,206 @@
<template>
<view class="workcase-creator-mask" @tap="onClose" v-if="show">
<view class="workcase-creator" @tap.stop>
<view class="creator-header">
<text class="header-title">创建工单</text>
</view>
<scroll-view class="creator-content" scroll-y="true">
<view class="form-item">
<text class="label">工单标题</text>
<input class="input" v-model="form.title" placeholder="请输入工单标题" maxlength="50" />
</view>
<view class="form-item">
<text class="label">问题分类</text>
<picker class="picker" :value="categoryIndex" :range="categories" @change="onCategoryChange">
<view class="picker-content">
<text class="picker-text">{{categories[categoryIndex]}}</text>
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<text class="label">紧急程度</text>
<picker class="picker" :value="priorityIndex" :range="priorities" @change="onPriorityChange">
<view class="picker-content">
<text class="picker-text">{{priorities[priorityIndex]}}</text>
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<text class="label">问题描述</text>
<textarea class="textarea" v-model="form.description" placeholder="请详细描述遇到的问题..." maxlength="500" />
<text class="char-count">{{form.description.length}}/500</text>
</view>
<view class="form-item">
<text class="label">联系方式</text>
<input class="input" v-model="form.contact" placeholder="请输入您的联系方式" />
</view>
<view class="form-item">
<text class="label">上传图片</text>
<view class="upload-area">
<view class="upload-item" v-for="(item, index) in form.images" :key="index">
<image class="upload-image" :src="item" mode="aspectFill" />
<view class="delete-btn" @tap="deleteImage(index)">
<text class="delete-icon">×</text>
</view>
</view>
<view class="upload-btn" v-if="form.images.length < 3" @tap="chooseImage">
<text class="upload-plus">+</text>
<text class="upload-text">添加图片</text>
</view>
</view>
<text class="upload-tip">最多上传3张图片</text>
</view>
</scroll-view>
<view class="creator-footer">
<button class="cancel-btn" @tap="onCancel">取消</button>
<button class="submit-btn" @tap="onSubmit" :disabled="!canSubmit">提交工单</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from 'vue'
// 接口定义
interface WorkcaseForm {
title : string
description : string
contact : string
images : string[]
}
interface WorkcaseData extends WorkcaseForm {
category : string
priority : string
}
// Props 定义
interface Props {
show ?: boolean
}
const props = withDefaults(defineProps<Props>(), {
show: false
})
// Emits 定义
const emits = defineEmits<{
close : []
success : [data: WorkcaseData]
}>()
// 响应式数据
const form = ref<WorkcaseForm>({
title: '',
description: '',
contact: '',
images: []
})
const categories = ref<string[]>(['设施报修', '环境卫生', '交通问题', '安全隐患', '其他问题'])
const categoryIndex = ref<number>(0)
const priorities = ref<string[]>(['一般', '紧急', '非常紧急'])
const priorityIndex = ref<number>(0)
// 计算属性
const canSubmit = computed(() => {
return form.value.title.trim() &&
form.value.description.trim() &&
form.value.contact.trim()
})
// 方法定义
function onClose() {
emits('close')
}
function onCancel() {
resetForm()
emits('close')
}
function onCategoryChange(e : any) {
categoryIndex.value = e.detail.value
}
function onPriorityChange(e : any) {
priorityIndex.value = e.detail.value
}
function chooseImage() {
uni.chooseImage({
count: 3 - form.value.images.length,
sizeType: ['compressed'],
sourceType: ['camera', 'album'],
success: (res) => {
form.value.images.push(...res.tempFilePaths)
},
fail: (err) => {
console.log('选择图片失败:', err)
}
})
}
function deleteImage(index : number) {
form.value.images.splice(index, 1)
}
function onSubmit() {
if (!canSubmit.value) {
uni.showToast({
title: '请完善必填信息',
icon: 'none'
})
return
}
const workcaseData : WorkcaseData = {
title: form.value.title.trim(),
category: categories.value[categoryIndex.value],
priority: priorities.value[priorityIndex.value],
description: form.value.description.trim(),
contact: form.value.contact.trim(),
images: form.value.images
}
uni.showLoading({
title: '提交中...'
})
// 模拟提交
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '工单提交成功',
icon: 'success'
})
resetForm()
emits('success', workcaseData)
}, 1500)
}
function resetForm() {
form.value = {
title: '',
description: '',
contact: '',
images: []
}
categoryIndex.value = 0
priorityIndex.value = 0
}
</script>
<style lang="scss" scoped>
@import './WorkcaseCreator.scss';
</style>

View File

@@ -1,13 +1,11 @@
{ {
"name" : "test", "name" : "泰豪小电",
"appid" : "", "appid" : "",
"description" : "", "description" : "泰豪小电智能工单系统",
"versionName" : "1.0.0", "versionName" : "1.0.0",
"versionCode" : "100", "versionCode" : "100",
"uni-app-x" : {}, "uni-app-x" : {},
/* */
"quickapp" : {}, "quickapp" : {},
/* */
"mp-weixin" : { "mp-weixin" : {
"appid" : "", "appid" : "",
"setting" : { "setting" : {

View File

@@ -1,17 +1,42 @@
{ {
"pages": [ //pages数组中第一项表示应用启动页参考https://doc.dcloud.net.cn/uni-app-x/collocation/pagesjson.html "pages": [
{ {
"path": "pages/index/index", "path": "pages/index/index",
"style": { "style": {
"navigationBarTitleText": "uni-app x" "navigationStyle": "custom",
"backgroundColor": "#667eea",
"enablePullDownRefresh": false,
"safeAreaInsets": {
"top": true,
"bottom": false
}
}
},
{
"path": "pages/workcase/list",
"style": {
"navigationBarTitleText": "我的工单",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/workcase/detail",
"style": {
"navigationBarTitleText": "工单详情",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white"
} }
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "white",
"navigationBarTitleText": "uni-app x", "navigationBarTitleText": "泰豪小电",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#667eea",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F9FA"
}, },
"uniIdRouter": {} "uniIdRouter": {
"loginPage": "pages/index/index",
"needLogin": []
}
} }

View File

@@ -0,0 +1,347 @@
// 主容器
.chat-container {
height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(180deg, #E8F4FD 0%, #F5FAFF 25%, #FAFCFE 50%, #FFFFFF 100%);
position: relative;
// 多种安全区域适配方式
padding-top: env(safe-area-inset-top);
padding-top: constant(safe-area-inset-top); /* 兼容iOS < 11.2 */
box-sizing: border-box;
}
// 顶部标题栏
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 16px;
// background: linear-gradient(180deg, rgba(235, 245, 255, 0.8) 0%, rgba(255, 255, 255, 0.95) 100%);
// backdrop-filter: blur(10px);
z-index: 100;
box-sizing: border-box;
// paddingTop和height通过JS动态设置
// 小程序需要为右侧胶囊按钮留出空间
/* #ifdef MP-WEIXIN */
padding-right: 100px; // 为胶囊按钮留出空间
/* #endif */
}
.title {
font-size: 20px; // 调整字体大小以适配胶囊按钮高度
font-weight: bold;
color: #000000;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
line-height: 1;
}
.workcase-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 6px;
padding: 6px 12px;
height: 32px;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
backdrop-filter: blur(10px);
box-sizing: border-box;
white-space: nowrap;
flex-shrink: 0; // 防止按钮被压缩
}
.btn-icon {
width: 16px;
height: 16px;
}
.btn-text {
color: #000000;
font-size: 14px;
font-weight: 500;
}
// 聊天消息区域
.chat-messages {
flex: 1;
// background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(10px);
padding: 20px 16px;
padding-bottom: 120px;
position: relative;
// 为固定定位的header留出空间
margin-top: 76px; // 默认header高度
}
// 欢迎界面
.welcome-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400px;
text-align: center;
padding: 40px 20px;
}
.welcome-image {
width: 90%; // 增大图片尺寸以适应PNG图标+文字气泡
margin-bottom: 32px;
}
.welcome-text-primary {
font-size: 22px; // 增大主标题字体
font-weight: 600;
color: #1A1A1A; // 更深的文字颜色
margin-bottom: 12px;
line-height: 1.3;
}
.welcome-text-secondary {
font-size: 16px; // 增大副标题字体
color: #4A4A4A; // 调整副标题颜色
line-height: 1.4;
}
// 消息列表
.messages-list {
padding: 16px 0;
}
.message-item {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.user-message-content {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-end;
gap: 8px;
}
.bot-message-content {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 8px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.user-avatar {
background: #5B8FF9;
}
.bot-avatar {
background: #FF6B6B;
}
.avatar-text {
font-size: 14px;
font-weight: 500;
color: #FFFFFF;
}
.message-bubble {
max-width: 240px;
padding: 10px 14px;
border-radius: 12px;
position: relative;
}
.user-bubble {
background: #5B8FF9;
border-radius: 12px;
}
.bot-bubble {
background: #FFFFFF;
border: 1px solid #E5E5E5;
border-radius: 12px;
}
.user-bubble .message-text {
color: #FFFFFF;
}
.bot-bubble .message-text {
color: #333333;
}
.message-text {
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
}
.message-time {
font-size: 11px;
color: #999999;
margin-top: 4px;
}
.user-message .message-time {
text-align: right;
padding-right: 48px;
}
.bot-message .message-time {
text-align: left;
padding-left: 48px;
}
// 底部操作区域
.bottom-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
// background: #FFFFFF;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
// box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
z-index: 50;
}
// 第一行容器
.top-row {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 12px;
}
// 主要操作按钮
.main-actions {
display: flex;
flex-direction: row;
gap: 8px;
flex-shrink: 0;
}
.action-btn {
height: 30px;
padding: 0 20px;
border-radius: 20px;
border: none;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
flex-shrink: 0;
}
.action-btn.primary {
background: #5B8FF9;
color: #FFFFFF;
}
.action-btn.secondary {
background: #F7F8FA;
color: #1F2329;
border: 1px solid #E5E6EB;
}
.action-text {
font-size: 14px;
font-weight: 500;
}
// 竖向分隔线
.divider-line {
width: 1px;
height: 30px;
background: #E5E6EB;
margin: 0 12px;
flex-shrink: 0;
}
// 快速问题区域
.quick-section {
display: flex;
flex-direction: row;
flex: 1;
}
.quick-btn {
// width: 100%;
height: 30px;
background: #F7F8FA;
border: 1px solid #E5E6EB;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.quick-text {
font-size: 13px;
color: #646A73;
}
// 输入区域
.input-section {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
}
.chat-input {
flex: 1;
height: 40px;
padding: 0 50px 0 16px;
background: #F7F8FA;
border: 1px solid #E5E6EB;
border-radius: 20px;
font-size: 14px;
color: #1F2329;
box-sizing: border-box;
}
.chat-input::placeholder {
color: #8F959E;
}
.add-btn {
position: absolute;
right: 4px;
width: 32px;
height: 32px;
border-radius: 16px;
border: 2px solid;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transform: scale(0.7);
}
.add-icon {
font-size: 18px;
font-weight: 400;
color: #000000;
line-height: 1;
}

View File

@@ -1,36 +1,366 @@
<template> <template>
<view> <view class="chat-container">
<image class="logo" src="/static/logo.png"></image> <!-- 顶部标题栏 -->
<text class="title">{{title}}</text> <view class="header" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<text class="title">泰豪小电</text>
<button class="workcase-btn" @tap="goToWorkList">
<image class="btn-icon" src="/static/imgs/case.svg" />
<text class="btn-text">我的工单</text>
</button>
</view>
<!-- 聊天消息区域 -->
<scroll-view class="chat-messages" scroll-y="true" :scroll-top="scrollTop" scroll-with-animation="true">
<!-- 默认欢迎界面 -->
<view class="welcome-container" v-if="messages.length === 0">
<image class="welcome-image" src="/static/imgs/defaultchat.png" />
<text class="welcome-text-primary">Hi~ 有什么可以帮您!</text>
<text class="welcome-text-secondary">泰豪小电为您服务:)</text>
</view>
<!-- 聊天消息列表 -->
<view class="messages-list" v-else>
<view class="message-item" v-for="(item, index) in messages" :key="index"
:class="item.type === 'user' ? 'user-message' : 'bot-message'">
<!-- 用户消息(右侧) -->
<view class="user-message-content" v-if="item.type === 'user'">
<view class="message-bubble user-bubble">
<text class="message-text">{{item.content}}</text>
</view>
<view class="avatar user-avatar">
<text class="avatar-text">我</text>
</view>
</view>
<!-- Bot/员工消息(左侧) -->
<view class="bot-message-content" v-else>
<view class="avatar bot-avatar">
<text class="avatar-text">AI</text>
</view>
<view class="message-bubble bot-bubble">
<text class="message-text">{{item.content}}</text>
</view>
</view>
<text class="message-time">{{item.time}}</text>
</view>
</view>
</scroll-view>
<!-- 底部操作区域 -->
<view class="bottom-area">
<!-- 第一行:按钮和快速问题 -->
<view class="top-row">
<view class="main-actions">
<button class="action-btn primary" @tap="contactHuman">
<text class="action-text">联系人工</text>
</button>
<button class="action-btn secondary" @tap="showCreator">
<text class="action-text">创建工单</text>
</button>
</view>
<!-- 竖向分隔线 -->
<view class="divider-line"></view>
<!-- 快速问题 -->
<view class="quick-section">
<button class="quick-btn" @tap="handleQuickQuestion">
<text class="quick-text">查询质保状态</text>
</button>
</view>
</view>
<!-- 输入区域 -->
<view class="input-section">
<input class="chat-input" v-model="inputText" placeholder="输入问题来问我~" @confirm="sendMessage" />
<button class="add-btn" @tap="showUploadOptions">
<text class="add-icon">+</text>
</button>
</view>
</view>
<!-- 工单创建弹窗 -->
<WorkcaseCreator v-if="showWorkcaseCreator" :show="showWorkcaseCreator" @close="hideCreator"
@success="onWorkcaseCreated" />
</view> </view>
</template> </template>
<script> <script setup lang="ts">
export default { import { ref, nextTick, onMounted } from 'vue'
data() { import WorkcaseCreator from '@/components/WorkcaseCreator/WorkcaseCreator.uvue'
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
// 接口定义
interface Message {
type : 'user' | 'bot'
content : string
time : string
actions ?: string[] | null
} }
interface WorkcaseData {
title : string
category : string
priority : string
description : string
contact : string
images : string[]
}
// 响应式数据
const messages = ref<Message[]>([])
const inputText = ref<string>('')
const isTyping = ref<boolean>(false)
const scrollTop = ref<number>(0)
const showWorkcaseCreator = ref<boolean>(false)
const statusBarHeight = ref<number>(0)
const headerPaddingTop = ref<number>(44) // header顶部padding默认44px
const headerTotalHeight = ref<number>(76) // header总高度默认76px
// 生命周期
onMounted(() => {
// 设置页面标题
uni.setNavigationBarTitle({
title: '智能助手'
})
// 获取系统信息和安全区域
uni.getSystemInfo({
success: (res) => {
console.log('系统信息:', res)
console.log('状态栏高度:', res.statusBarHeight)
statusBarHeight.value = res.statusBarHeight || 0
console.log('安全区域:', res.safeArea)
console.log('安全区域insets:', res.safeAreaInsets)
// #ifdef MP-WEIXIN
// 获取胶囊按钮信息仅小程序计算header位置
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
console.log('胶囊按钮信息:', menuButtonInfo)
// 计算header的paddingTop和总高度
// paddingTop = 胶囊按钮的top值
// 总高度 = 胶囊按钮bottom值
headerPaddingTop.value = menuButtonInfo.top
headerTotalHeight.value = menuButtonInfo.bottom
console.log('header paddingTop:', headerPaddingTop.value)
console.log('header totalHeight:', headerTotalHeight.value)
} catch (e) {
console.log('获取胶囊按钮信息失败:', e)
// 使用默认值
headerPaddingTop.value = 44
headerTotalHeight.value = 76
}
// #endif
}
})
})
// 发送消息
function sendMessage() {
const text = inputText.value.trim()
if (!text || isTyping.value) return
// 添加用户消息
addMessage('user', text)
inputText.value = ''
// 模拟AI回复
simulateAIResponse(text)
}
// 添加消息
function addMessage(type : 'user' | 'bot', content : string, actions : string[] | null = null) {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
messages.value.push({
type,
content,
time,
actions
})
// 滚动到底部
nextTick(() => {
scrollToBottom()
})
}
// 模拟AI回复
function simulateAIResponse(userMessage : string) {
isTyping.value = true
setTimeout(() => {
isTyping.value = false
let response = ''
let actions : string[] | null = null
// 根据用户输入生成回复
if (userMessage.includes('工单') || userMessage.includes('报修') || userMessage.includes('问题')) {
response = '我理解您需要处理工单相关的事务。我可以帮您:'
actions = ['创建新工单', '查看工单状态', '联系客服']
} else if (userMessage.includes('你好') || userMessage.includes('您好')) {
response = '您好!很高兴为您服务。请问有什么可以帮助您的吗?'
actions = ['创建工单', '查看工单', '常见问题']
} else if (userMessage.includes('帮助') || userMessage.includes('功能')) {
response = '我可以为您提供以下服务:\n1. 创建工单 - 报告问题或提交服务请求\n2. 查看工单 - 跟踪您的工单处理进度\n3. 智能问答 - 解答常见问题'
actions = ['创建工单', '查看工单']
} else {
response = '感谢您的咨询。如果您遇到具体问题,建议创建工单,我们的专业团队会尽快为您处理。'
actions = ['创建工单', '联系人工客服']
}
addMessage('bot', response, actions)
}, 1000 + Math.random() * 1000)
}
// 快捷操作
function quickAction(action : string) {
if (action === '创建工单') {
showCreator()
} else if (action === '查看工单') {
goToWorkList()
} else {
addMessage('user', action)
simulateAIResponse(action)
}
}
// 处理建议操作
function handleSuggestedAction(action : string) {
if (action === '创建工单' || action === '创建新工单') {
showCreator()
} else if (action === '查看工单' || action === '查看工单状态') {
goToWorkList()
} else if (action === '联系客服' || action === '联系人工客服') {
uni.showModal({
title: '联系客服',
content: '客服电话400-123-4567\n工作时间9:00-18:00',
showCancel: false
})
} else {
addMessage('user', action)
simulateAIResponse(action)
}
}
// 显示工单创建器
function showCreator() {
showWorkcaseCreator.value = true
}
// 隐藏工单创建器
function hideCreator() {
showWorkcaseCreator.value = false
}
// 工单创建成功
function onWorkcaseCreated(workcaseData : WorkcaseData) {
hideCreator()
uni.showToast({
title: '工单创建成功',
icon: 'success'
})
// 添加成功消息
addMessage('bot', `工单创建成功!\n标题${workcaseData.title}\n分类${workcaseData.category}\n我们会尽快处理您的问题。`, ['查看工单', '创建新工单'])
}
// 跳转到工单列表
function goToWorkList() {
uni.navigateTo({
url: '/pages/workcase/list'
})
}
// 滚动到底部
function scrollToBottom() {
scrollTop.value = 999999
}
// 联系人工客服
function contactHuman() {
uni.showModal({
title: '联系人工客服',
content: '客服电话400-123-4567\n工作时间9:00-18:00\n\n是否拨打电话',
confirmText: '拨打',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.makePhoneCall({
phoneNumber: '400-123-4567'
})
}
}
})
}
// 处理快速问题
function handleQuickQuestion() {
addMessage('user', '查询质保状态')
simulateAIResponse('查询质保状态')
}
// 显示上传选项
function showUploadOptions() {
uni.showActionSheet({
itemList: ['拍照', '从相册选择', '选择文件'],
success: (res) => {
switch (res.tapIndex) {
case 0:
// 拍照
chooseImageFromCamera()
break
case 1:
// 从相册选择
chooseImageFromAlbum()
break
case 2:
// 选择文件
chooseFile()
break
}
}
})
}
// 拍照
function chooseImageFromCamera() {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: (res) => {
// 处理图片上传逻辑
console.log('选择的图片:', res.tempFilePaths)
addMessage('user', '[图片]')
simulateAIResponse('收到您发送的图片')
}
})
}
// 从相册选择
function chooseImageFromAlbum() {
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: (res) => {
// 处理图片上传逻辑
console.log('选择的图片:', res.tempFilePaths)
addMessage('user', '[图片]')
simulateAIResponse('收到您发送的图片')
}
})
}
// 选择文件
function chooseFile() {
// 这里可以扩展文件选择功能
uni.showToast({
title: '文件选择功能开发中',
icon: 'none'
})
} }
</script> </script>
<style> <style lang="scss" scoped>
.logo { @import './index.scss';
height: 100px;
width: 100px;
margin: 100px auto 25px auto;
}
.title {
font-size: 18px;
color: #8f8f94;
text-align: center;
}
</style> </style>

View File

@@ -0,0 +1,403 @@
.detail-container {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #F5F5F5;
}
.detail-content {
flex: 1;
padding: 16px;
padding-bottom: 80px;
}
.info-card {
background-color: #FFFFFF;
margin-bottom: 16px;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #F0F0F0;
}
.card-title {
font-size: 18px;
font-weight: 600;
color: #333333;
}
.status-tag {
padding: 6px 12px;
border-radius: 16px;
}
.status-pending {
background-color: #FFF3E0;
color: #F57C00;
}
.status-processing {
background-color: #E3F2FD;
color: #1976D2;
}
.status-completed {
background-color: #E8F5E8;
color: #388E3C;
}
.status-cancelled {
background-color: #FFEBEE;
color: #D32F2F;
}
.status-text {
font-size: 14px;
font-weight: 500;
}
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
}
.label {
width: 80px;
font-size: 14px;
color: #666666;
line-height: 1.4;
}
.value {
flex: 1;
font-size: 14px;
color: #333333;
line-height: 1.4;
}
.priority {
font-weight: 500;
}
.priority-normal {
color: #666666;
}
.priority-urgent {
color: #FF9800;
}
.priority-emergency {
color: #F44336;
}
.description {
font-size: 16px;
color: #333333;
line-height: 1.6;
}
.image-gallery {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.gallery-image {
width: 80px;
height: 80px;
border-radius: 8px;
}
.progress-text {
font-size: 14px;
font-weight: 500;
color: #1976D2;
}
.progress-container {
margin: 12px 0;
}
.progress-bar {
width: 100%;
height: 6px;
background-color: #E0E0E0;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #1976D2;
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-desc {
font-size: 12px;
color: #666666;
}
.timeline {
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: 0;
width: 2px;
background-color: #E0E0E0;
}
.timeline-item {
position: relative;
display: flex;
align-items: flex-start;
margin-bottom: 20px;
}
.timeline-dot {
width: 20px;
height: 20px;
border-radius: 10px;
margin-right: 16px;
border: 3px solid #FFFFFF;
box-shadow: 0 0 0 2px #E0E0E0;
flex-shrink: 0;
}
.dot-create {
background-color: #4CAF50;
}
.dot-accept {
background-color: #2196F3;
}
.dot-processing {
background-color: #FF9800;
}
.dot-complete {
background-color: #4CAF50;
}
.timeline-content {
flex: 1;
padding-top: 2px;
}
.record-title {
font-size: 16px;
font-weight: 500;
color: #333333;
line-height: 1.4;
}
.record-desc {
display: block;
font-size: 14px;
color: #666666;
margin: 4px 0;
line-height: 1.4;
}
.record-meta {
display: flex;
justify-content: space-between;
margin-top: 8px;
}
.record-time {
font-size: 12px;
color: #999999;
}
.record-operator {
font-size: 12px;
color: #999999;
}
.rating-section {
text-align: center;
}
.stars {
margin-bottom: 12px;
}
.star {
font-size: 24px;
color: #FFD700;
margin: 0 2px;
}
.rating-text {
font-size: 14px;
color: #666666;
line-height: 1.4;
}
.bottom-actions {
background-color: #FFFFFF;
padding: 16px;
border-top: 1px solid #E0E0E0;
display: flex;
gap: 12px;
}
.action-btn {
flex: 1;
height: 44px;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
border: none;
}
.action-btn.primary {
background-color: #1976D2;
color: #FFFFFF;
}
.action-btn.secondary {
background-color: #F0F0F0;
color: #666666;
}
.rating-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.modal-content {
width: 90%;
max-width: 400px;
background-color: #FFFFFF;
border-radius: 12px;
overflow: hidden;
}
.modal-header {
padding: 20px 16px 16px;
border-bottom: 1px solid #F0F0F0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333333;
}
.close-btn {
width: 28px;
height: 28px;
border-radius: 14px;
background-color: #F0F0F0;
display: flex;
align-items: center;
justify-content: center;
}
.close-icon {
color: #666666;
font-size: 20px;
line-height: 1;
}
.rating-form {
padding: 20px 16px;
}
.form-label {
display: block;
font-size: 16px;
color: #333333;
margin-bottom: 12px;
}
.star-rating {
text-align: center;
margin-bottom: 20px;
}
.rating-star {
font-size: 32px;
color: #E0E0E0;
margin: 0 4px;
}
.rating-star.active {
color: #FFD700;
}
.rating-textarea {
width: 100%;
min-height: 80px;
padding: 12px;
border: 1px solid #E0E0E0;
border-radius: 8px;
font-size: 14px;
resize: none;
}
.char-count {
color: #999999;
font-size: 12px;
text-align: right;
margin-top: 4px;
}
.modal-actions {
padding: 16px;
border-top: 1px solid #F0F0F0;
display: flex;
gap: 12px;
}
.modal-btn {
flex: 1;
height: 40px;
border-radius: 20px;
font-size: 16px;
border: none;
}
.modal-btn.cancel {
background-color: #F0F0F0;
color: #666666;
}
.modal-btn.confirm {
background-color: #1976D2;
color: #FFFFFF;
}
.modal-btn[disabled] {
background-color: #CCCCCC;
color: #999999;
}

View File

@@ -0,0 +1,507 @@
<template>
<view class="detail-container">
<scroll-view class="detail-content" scroll-y="true">
<!-- 工单基本信息 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">基本信息</text>
<view class="status-tag" :class="getStatusClass(workcase.status)">
<text class="status-text">{{workcase.statusText}}</text>
</view>
</view>
<view class="info-item">
<text class="label">工单标题</text>
<text class="value">{{workcase.title}}</text>
</view>
<view class="info-item">
<text class="label">工单编号</text>
<text class="value">{{workcase.number}}</text>
</view>
<view class="info-item">
<text class="label">问题分类</text>
<text class="value">{{workcase.category}}</text>
</view>
<view class="info-item">
<text class="label">紧急程度</text>
<text class="value priority" :class="getPriorityClass(workcase.priority)">
{{workcase.priority}}
</text>
</view>
<view class="info-item">
<text class="label">联系方式</text>
<text class="value">{{workcase.contact}}</text>
</view>
<view class="info-item">
<text class="label">创建时间</text>
<text class="value">{{formatDateTime(workcase.createTime)}}</text>
</view>
</view>
<!-- 问题描述 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">问题描述</text>
</view>
<text class="description">{{workcase.description}}</text>
</view>
<!-- 图片附件 -->
<view class="info-card" v-if="workcase.images && workcase.images.length > 0">
<view class="card-header">
<text class="card-title">相关图片</text>
</view>
<view class="image-gallery">
<image
class="gallery-image"
v-for="(image, index) in workcase.images"
:key="index"
:src="image"
mode="aspectFill"
@tap="previewImage(index)"
/>
</view>
</view>
<!-- 处理进度 -->
<view class="info-card" v-if="workcase.status === 'processing'">
<view class="card-header">
<text class="card-title">处理进度</text>
<text class="progress-text">{{workcase.progress}}%</text>
</view>
<view class="progress-container">
<view class="progress-bar">
<view class="progress-fill" :style="'width: ' + workcase.progress + '%'"></view>
</view>
</view>
<text class="progress-desc">{{getProgressDesc(workcase.progress)}}</text>
</view>
<!-- 处理记录 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">处理记录</text>
</view>
<view class="timeline">
<view
class="timeline-item"
v-for="(record, index) in workcase.records"
:key="index"
>
<view class="timeline-dot" :class="getRecordDotClass(record.type)"></view>
<view class="timeline-content">
<text class="record-title">{{record.title}}</text>
<text class="record-desc" v-if="record.description">{{record.description}}</text>
<view class="record-meta">
<text class="record-time">{{formatDateTime(record.time)}}</text>
<text class="record-operator">{{record.operator}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 客服评价 -->
<view class="info-card" v-if="workcase.rating">
<view class="card-header">
<text class="card-title">服务评价</text>
</view>
<view class="rating-section">
<view class="stars">
<text
class="star"
v-for="i in 5"
:key="i"
:class="{active: i <= workcase.rating.score}"
>★</text>
</view>
<text class="rating-text">{{workcase.rating.comment}}</text>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<button
class="action-btn secondary"
@tap="contactService"
>
联系客服
</button>
<button
class="action-btn primary"
v-if="workcase.status === 'pending'"
@tap="cancelWorkcase"
>
取消工单
</button>
<button
class="action-btn primary"
v-if="workcase.status === 'processing'"
@tap="confirmComplete"
>
确认完成
</button>
<button
class="action-btn primary"
v-if="workcase.status === 'completed' && !workcase.rating"
@tap="showRating"
>
服务评价
</button>
</view>
<!-- 评价弹窗 -->
<view class="rating-modal" v-if="showRatingModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">服务评价</text>
<view class="close-btn" @tap="hideRating">
<text class="close-icon">×</text>
</view>
</view>
<view class="rating-form">
<text class="form-label">请为本次服务打分</text>
<view class="star-rating">
<text
class="rating-star"
v-for="i in 5"
:key="i"
:class="{active: i <= ratingScore}"
@tap="setRating(i)"
>★</text>
</view>
<text class="form-label">评价内容</text>
<textarea
class="rating-textarea"
v-model="ratingComment"
placeholder="请输入您的评价内容..."
maxlength="200"
/>
<text class="char-count">{{ratingComment.length}}/200</text>
</view>
<view class="modal-actions">
<button class="modal-btn cancel" @tap="hideRating">取消</button>
<button
class="modal-btn confirm"
@tap="submitRating"
:disabled="ratingScore === 0"
>提交</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 接口定义
interface Workcase {
id: string
number: string
title: string
category: string
priority: string
status: 'pending' | 'processing' | 'completed' | 'cancelled'
statusText: string
description: string
contact: string
progress: number
createTime: Date
updateTime: Date
images?: string[]
records: ProcessRecord[]
rating?: Rating
}
interface ProcessRecord {
type: string
title: string
description?: string
time: Date
operator: string
}
interface Rating {
score: number
comment: string
}
// 响应式数据
const workcaseId = ref<string | null>(null)
const workcase = ref<Workcase>({} as Workcase)
const showRatingModal = ref<boolean>(false)
const ratingScore = ref<number>(0)
const ratingComment = ref<string>('')
// 生命周期
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
workcaseId.value = currentPage.options?.id || '1'
loadWorkcaseDetail()
})
// 方法定义
async function loadWorkcaseDetail() {
try {
// 模拟获取工单详情
workcase.value = getMockWorkcase()
// 设置页面标题
uni.setNavigationBarTitle({
title: workcase.value.title
})
} catch (error) {
uni.showToast({
title: '加载失败',
icon: 'error'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
}
function getMockWorkcase(): Workcase {
return {
id: workcaseId.value || '1',
number: `WC2024${String(workcaseId.value || '1').padStart(4, '0')}`,
title: '小区公园路灯不亮需要维修',
category: '设施报修',
priority: '紧急',
status: 'processing',
statusText: '处理中',
description: '小区公园内的路灯已经连续三天不亮了,影响居民夜间出行安全。路灯位置在公园主干道上,希望能够尽快派人维修。',
contact: '138****5678',
progress: 65,
createTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
updateTime: new Date(),
images: [
'/static/workcase1.jpg',
'/static/workcase2.jpg'
],
records: [
{
type: 'create',
title: '工单创建',
description: '用户提交工单,问题已记录',
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
operator: '系统'
},
{
type: 'accept',
title: '工单受理',
description: '客服已受理,安排相关人员处理',
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 + 30 * 60 * 1000),
operator: '客服小王'
},
{
type: 'processing',
title: '现场勘查',
description: '维修人员已到达现场,正在检查路灯故障原因',
time: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
operator: '维修师傅张三'
},
{
type: 'processing',
title: '配件采购',
description: '故障原因确认为灯泡损坏,正在采购替换配件',
time: new Date(Date.now() - 12 * 60 * 60 * 1000),
operator: '维修师傅张三'
}
]
}
}
// 获取状态样式
function getStatusClass(status: string) {
return {
'status-pending': status === 'pending',
'status-processing': status === 'processing',
'status-completed': status === 'completed',
'status-cancelled': status === 'cancelled'
}
}
// 获取优先级样式
function getPriorityClass(priority: string) {
return {
'priority-normal': priority === '一般',
'priority-urgent': priority === '紧急',
'priority-emergency': priority === '非常紧急'
}
}
// 获取记录点样式
function getRecordDotClass(type: string) {
return {
'dot-create': type === 'create',
'dot-accept': type === 'accept',
'dot-processing': type === 'processing',
'dot-complete': type === 'complete'
}
}
// 获取进度描述
function getProgressDesc(progress: number): string {
if (progress < 25) {
return '刚刚开始处理'
} else if (progress < 50) {
return '正在积极处理中'
} else if (progress < 75) {
return '处理进展顺利'
} else if (progress < 100) {
return '即将完成处理'
} else {
return '处理已完成'
}
}
// 格式化日期时间
function formatDateTime(date: Date): string {
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hour = String(d.getHours()).padStart(2, '0')
const minute = String(d.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
// 预览图片
function previewImage(index: number) {
uni.previewImage({
current: index,
urls: workcase.value.images || []
})
}
// 联系客服
function contactService() {
uni.showActionSheet({
itemList: ['拨打电话', '在线客服'],
success: (res) => {
if (res.tapIndex === 0) {
uni.makePhoneCall({
phoneNumber: '400-123-4567'
})
} else {
uni.navigateTo({
url: '/pages/index/index'
})
}
}
})
}
// 取消工单
function cancelWorkcase() {
uni.showModal({
title: '确认取消',
content: '确定要取消此工单吗?取消后无法恢复。',
success: (res) => {
if (res.confirm) {
workcase.value.status = 'cancelled'
workcase.value.statusText = '已取消'
// 添加取消记录
workcase.value.records.push({
type: 'cancel',
title: '工单取消',
description: '用户主动取消工单',
time: new Date(),
operator: '用户'
})
uni.showToast({
title: '工单已取消',
icon: 'success'
})
}
}
})
}
// 确认完成
function confirmComplete() {
uni.showModal({
title: '确认完成',
content: '确认问题已经得到解决?',
success: (res) => {
if (res.confirm) {
workcase.value.status = 'completed'
workcase.value.statusText = '已完成'
workcase.value.progress = 100
// 添加完成记录
workcase.value.records.push({
type: 'complete',
title: '工单完成',
description: '用户确认问题已解决',
time: new Date(),
operator: '用户'
})
uni.showToast({
title: '工单已完成',
icon: 'success'
})
}
}
})
}
// 显示评价弹窗
function showRating() {
showRatingModal.value = true
ratingScore.value = 0
ratingComment.value = ''
}
// 隐藏评价弹窗
function hideRating() {
showRatingModal.value = false
}
// 设置评分
function setRating(score: number) {
ratingScore.value = score
}
// 提交评价
function submitRating() {
if (ratingScore.value === 0) {
uni.showToast({
title: '请选择评分',
icon: 'none'
})
return
}
workcase.value.rating = {
score: ratingScore.value,
comment: ratingComment.value || '用户未填写评价内容'
}
hideRating()
uni.showToast({
title: '评价提交成功',
icon: 'success'
})
}
</script>
<style lang="scss" scoped>
@import './detail.scss';
</style>

View File

@@ -0,0 +1,293 @@
.workcase-list-container {
height: 100vh;
background-color: #F5F5F5;
display: flex;
flex-direction: column;
}
.filter-bar {
background-color: #FFFFFF;
padding: 12px 16px;
display: flex;
gap: 12px;
border-bottom: 1px solid #E0E0E0;
}
.filter-picker {
flex: 1;
}
.picker-content {
padding: 8px 12px;
border: 1px solid #E0E0E0;
border-radius: 6px;
background-color: #F8F8F8;
display: flex;
justify-content: space-between;
align-items: center;
}
.picker-text {
color: #333333;
font-size: 14px;
}
.picker-arrow {
color: #999999;
font-size: 12px;
}
.workcase-list {
flex: 1;
padding: 0 16px;
}
.stats-card {
background-color: #FFFFFF;
margin: 16px 0;
padding: 16px;
border-radius: 12px;
display: flex;
justify-content: space-around;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stat-item {
text-align: center;
}
.stat-number {
display: block;
font-size: 24px;
font-weight: bold;
color: #1976D2;
line-height: 1.2;
}
.stat-label {
font-size: 12px;
color: #666666;
margin-top: 4px;
}
.workcase-card {
background-color: #FFFFFF;
margin-bottom: 12px;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-header {
margin-bottom: 12px;
}
.title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.workcase-title {
flex: 1;
font-size: 16px;
font-weight: 500;
color: #333333;
line-height: 1.4;
}
.status-tag {
padding: 4px 8px;
border-radius: 12px;
margin-left: 12px;
}
.status-pending {
background-color: #FFF3E0;
color: #F57C00;
}
.status-processing {
background-color: #E3F2FD;
color: #1976D2;
}
.status-completed {
background-color: #E8F5E8;
color: #388E3C;
}
.status-cancelled {
background-color: #FFEBEE;
color: #D32F2F;
}
.status-text {
font-size: 12px;
font-weight: 500;
}
.workcase-id {
font-size: 12px;
color: #999999;
}
.card-content {
margin-bottom: 12px;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 4px;
}
.info-label {
font-size: 14px;
color: #666666;
width: 80px;
}
.info-value {
font-size: 14px;
color: #333333;
flex: 1;
}
.priority {
font-weight: 500;
}
.priority-normal {
color: #666666;
}
.priority-urgent {
color: #FF9800;
}
.priority-emergency {
color: #F44336;
}
.card-description {
margin-bottom: 12px;
}
.description-text {
font-size: 14px;
color: #666666;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
.card-actions {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.action-btn {
flex: 1;
height: 32px;
border-radius: 16px;
font-size: 14px;
border: none;
}
.action-btn.primary {
background-color: #1976D2;
color: #FFFFFF;
}
.action-btn.secondary {
background-color: #F0F0F0;
color: #666666;
}
.progress-bar {
width: 100%;
height: 4px;
background-color: #E0E0E0;
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #1976D2;
border-radius: 2px;
transition: width 0.3s ease;
}
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-icon {
width: 120px;
height: 120px;
margin-bottom: 16px;
}
.empty-text {
font-size: 16px;
color: #999999;
margin-bottom: 24px;
}
.create-btn {
width: 140px;
height: 44px;
background-color: #1976D2;
color: #FFFFFF;
border-radius: 22px;
border: none;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.create-icon {
font-size: 20px;
font-weight: bold;
}
.load-more {
text-align: center;
padding: 20px;
}
.load-text {
color: #999999;
font-size: 14px;
}
.fab {
position: fixed;
bottom: 80px;
right: 20px;
width: 56px;
height: 56px;
border-radius: 28px;
background-color: #1976D2;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.4);
z-index: 10;
}
.fab-icon {
color: #FFFFFF;
font-size: 24px;
font-weight: bold;
}

View File

@@ -0,0 +1,443 @@
<template>
<view class="workcase-list-container">
<!-- 筛选栏 -->
<view class="filter-bar">
<picker
class="filter-picker"
:value="statusIndex"
:range="statusOptions"
@change="onStatusChange"
>
<view class="picker-content">
<text class="picker-text">{{statusOptions[statusIndex]}}</text>
<text class="picker-arrow">▼</text>
</view>
</picker>
<picker
class="filter-picker"
:value="categoryIndex"
:range="categoryOptions"
@change="onCategoryChange"
>
<view class="picker-content">
<text class="picker-text">{{categoryOptions[categoryIndex]}}</text>
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<!-- 工单列表 -->
<scroll-view
class="workcase-list"
scroll-y="true"
@scrolltolower="loadMore"
:refresher-enabled="true"
@refresherrefresh="onRefresh"
:refresher-triggered="isRefreshing"
>
<!-- 统计信息 -->
<view class="stats-card">
<view class="stat-item">
<text class="stat-number">{{stats.total}}</text>
<text class="stat-label">总工单</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.pending}}</text>
<text class="stat-label">待处理</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.processing}}</text>
<text class="stat-label">处理中</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.completed}}</text>
<text class="stat-label">已完成</text>
</view>
</view>
<!-- 工单卡片列表 -->
<view
class="workcase-card"
v-for="workcase in displayList"
:key="workcase.id"
@tap="goToDetail(workcase)"
>
<view class="card-header">
<view class="title-row">
<text class="workcase-title">{{workcase.title}}</text>
<view class="status-tag" :class="getStatusClass(workcase.status)">
<text class="status-text">{{workcase.statusText}}</text>
</view>
</view>
<text class="workcase-id">工单编号:{{workcase.number}}</text>
</view>
<view class="card-content">
<view class="info-row">
<text class="info-label">分类:</text>
<text class="info-value">{{workcase.category}}</text>
</view>
<view class="info-row">
<text class="info-label">紧急程度:</text>
<text class="info-value priority" :class="getPriorityClass(workcase.priority)">
{{workcase.priority}}
</text>
</view>
<view class="info-row">
<text class="info-label">创建时间:</text>
<text class="info-value">{{formatTime(workcase.createTime)}}</text>
</view>
</view>
<view class="card-description">
<text class="description-text">{{workcase.description}}</text>
</view>
<!-- 操作按钮 -->
<view class="card-actions" v-if="workcase.status !== 'completed'">
<button
class="action-btn secondary"
v-if="workcase.status === 'pending'"
@tap.stop="cancelWorkcase(workcase)"
>
取消工单
</button>
<button
class="action-btn primary"
v-if="workcase.status === 'processing'"
@tap.stop="confirmComplete(workcase)"
>
确认完成
</button>
<button
class="action-btn secondary"
@tap.stop="contactService(workcase)"
>
联系客服
</button>
</view>
<!-- 进度条 -->
<view class="progress-bar" v-if="workcase.status === 'processing'">
<view class="progress-fill" :style="'width: ' + workcase.progress + '%'"></view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="displayList.length === 0 && !isLoading">
<image class="empty-icon" src="/static/empty-workcase.png" mode="aspectFit" />
<text class="empty-text">暂无工单记录</text>
<button class="create-btn" @tap="createWorkcase">
<text class="create-icon">+</text>
<text>创建工单</text>
</button>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore">
<text class="load-text">{{isLoading ? '加载中...' : '上拉加载更多'}}</text>
</view>
</scroll-view>
<!-- 悬浮按钮 -->
<view class="fab" @tap="createWorkcase">
<text class="fab-icon">+</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 接口定义
interface Workcase {
id: number
number: string
title: string
description: string
category: string
priority: string
status: 'pending' | 'processing' | 'completed' | 'cancelled'
statusText: string
progress: number
createTime: Date
updateTime: Date
}
interface Stats {
total: number
pending: number
processing: number
completed: number
}
// 响应式数据
const workcaseList = ref<Workcase[]>([])
const displayList = ref<Workcase[]>([])
const statusOptions = ref<string[]>(['全部状态', '待处理', '处理中', '已完成', '已取消'])
const statusIndex = ref<number>(0)
const categoryOptions = ref<string[]>(['全部分类', '设施报修', '环境卫生', '交通问题', '安全隐患', '其他问题'])
const categoryIndex = ref<number>(0)
const stats = ref<Stats>({
total: 0,
pending: 0,
processing: 0,
completed: 0
})
const isLoading = ref<boolean>(false)
const isRefreshing = ref<boolean>(false)
const hasMore = ref<boolean>(true)
const page = ref<number>(1)
// 生命周期
onMounted(() => {
loadData()
})
// 方法定义
// 加载数据
async function loadData() {
isLoading.value = true
try {
// 模拟数据
const mockData = generateMockData()
workcaseList.value = mockData
updateDisplayList()
updateStats()
} catch (error) {
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
isLoading.value = false
isRefreshing.value = false
}
}
// 生成模拟数据
function generateMockData(): Workcase[] {
const categories = ['设施报修', '环境卫生', '交通问题', '安全隐患', '其他问题']
const priorities = ['一般', '紧急', '非常紧急']
const statuses: ('pending' | 'processing' | 'completed' | 'cancelled')[] = ['pending', 'processing', 'completed', 'cancelled']
const statusTexts = {
pending: '待处理',
processing: '处理中',
completed: '已完成',
cancelled: '已取消'
}
const mockList: Workcase[] = []
for (let i = 1; i <= 15; i++) {
const status = statuses[Math.floor(Math.random() * statuses.length)]
mockList.push({
id: i,
number: `WC${new Date().getFullYear()}${String(i).padStart(4, '0')}`,
title: `测试工单${i}`,
description: `这是一个测试工单的描述内容,描述了具体的问题情况...`,
category: categories[Math.floor(Math.random() * categories.length)],
priority: priorities[Math.floor(Math.random() * priorities.length)],
status: status,
statusText: statusTexts[status],
progress: status === 'processing' ? Math.floor(Math.random() * 80) + 10 : 0,
createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
updateTime: new Date()
})
}
return mockList
}
// 更新显示列表
function updateDisplayList() {
let filtered = [...workcaseList.value]
// 状态筛选
if (statusIndex.value > 0) {
const statusMap: Record<number, string> = {
1: 'pending',
2: 'processing',
3: 'completed',
4: 'cancelled'
}
filtered = filtered.filter(item => item.status === statusMap[statusIndex.value])
}
// 分类筛选
if (categoryIndex.value > 0) {
const category = categoryOptions.value[categoryIndex.value]
filtered = filtered.filter(item => item.category === category)
}
displayList.value = filtered
}
// 更新统计信息
function updateStats() {
stats.value = {
total: workcaseList.value.length,
pending: workcaseList.value.filter(item => item.status === 'pending').length,
processing: workcaseList.value.filter(item => item.status === 'processing').length,
completed: workcaseList.value.filter(item => item.status === 'completed').length
}
}
// 状态筛选改变
function onStatusChange(e: any) {
statusIndex.value = e.detail.value
updateDisplayList()
}
// 分类筛选改变
function onCategoryChange(e: any) {
categoryIndex.value = e.detail.value
updateDisplayList()
}
// 下拉刷新
function onRefresh() {
isRefreshing.value = true
page.value = 1
loadData()
}
// 加载更多
function loadMore() {
if (!hasMore.value || isLoading.value) return
page.value++
// 模拟加载更多
setTimeout(() => {
if (page.value > 3) {
hasMore.value = false
}
}, 1000)
}
// 跳转到详情页
function goToDetail(workcase: Workcase) {
uni.navigateTo({
url: `/pages/workcase/detail?id=${workcase.id}`
})
}
// 获取状态样式
function getStatusClass(status: string) {
return {
'status-pending': status === 'pending',
'status-processing': status === 'processing',
'status-completed': status === 'completed',
'status-cancelled': status === 'cancelled'
}
}
// 获取优先级样式
function getPriorityClass(priority: string) {
return {
'priority-normal': priority === '一般',
'priority-urgent': priority === '紧急',
'priority-emergency': priority === '非常紧急'
}
}
// 格式化时间
function formatTime(date: Date) {
const now = new Date()
const diff = now.getTime() - new Date(date).getTime()
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (days === 0) {
const hours = Math.floor(diff / (1000 * 60 * 60))
if (hours === 0) {
const minutes = Math.floor(diff / (1000 * 60))
return `${minutes}分钟前`
}
return `${hours}小时前`
} else if (days === 1) {
return '昨天'
} else if (days < 7) {
return `${days}天前`
} else {
return new Date(date).toLocaleDateString()
}
}
// 取消工单
function cancelWorkcase(workcase: Workcase) {
uni.showModal({
title: '确认取消',
content: `确定要取消工单"${workcase.title}"吗?`,
success: (res) => {
if (res.confirm) {
workcase.status = 'cancelled'
workcase.statusText = '已取消'
updateStats()
updateDisplayList()
uni.showToast({
title: '工单已取消',
icon: 'success'
})
}
}
})
}
// 确认完成
function confirmComplete(workcase: Workcase) {
uni.showModal({
title: '确认完成',
content: `确认工单"${workcase.title}"已处理完成?`,
success: (res) => {
if (res.confirm) {
workcase.status = 'completed'
workcase.statusText = '已完成'
workcase.progress = 100
updateStats()
updateDisplayList()
uni.showToast({
title: '工单已完成',
icon: 'success'
})
}
}
})
}
// 联系客服
function contactService(workcase: Workcase) {
uni.showActionSheet({
itemList: ['拨打电话', '在线客服', '查看进度'],
success: (res) => {
switch (res.tapIndex) {
case 0:
uni.makePhoneCall({
phoneNumber: '400-123-4567'
})
break
case 1:
uni.navigateTo({
url: '/pages/index/index'
})
break
case 2:
goToDetail(workcase)
break
}
}
})
}
// 创建工单
function createWorkcase() {
uni.switchTab({
url: '/pages/index/index'
})
}
</script>
<style lang="scss" scoped>
@import './list.scss';
</style>

View File

@@ -0,0 +1,25 @@
{
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wx15e67484db6d431f",
"editorSetting": {}
}

View File

@@ -0,0 +1,14 @@
{
"libVersion": "3.12.1",
"projectname": "workcase_wechat",
"setting": {
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"compileHotReLoad": true
}
}

View File

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.6665 5V9C1.6665 11.5141 1.6665 12.7713 2.44755 13.5523C3.2286 14.3333 4.48568 14.3333 6.99984 14.3333H8.99984C11.514 14.3333 12.7711 14.3333 13.5521 13.5523C14.3332 12.7713 14.3332 11.5141 14.3332 9V5" stroke="#222222" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.57923 3.54303L1.6665 4.99996H14.3332L13.4984 3.60865C12.9292 2.6601 12.6447 2.18583 12.1862 1.92623C11.7277 1.66663 11.1746 1.66663 10.0684 1.66663H5.96898C4.88649 1.66663 4.34525 1.66663 3.89326 1.91683C3.44127 2.16703 3.15392 2.6257 2.57923 3.54303Z" stroke="#222222" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 4.99996V1.66663" stroke="#222222" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12H7.33333M4 10H6" stroke="#222222" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 982 B

View File

@@ -0,0 +1,16 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.80025 13.4888C4.58434 15.1038 5.89547 16.4146 7.5104 17.1989C7.41717 17.1536 7.32423 17.1078 7.23306 17.0591L5.45493 17.4544C4.31174 17.7084 3.29152 16.6882 3.54556 15.545L3.94087 13.7669C3.89196 13.6754 3.84566 13.5823 3.80025 13.4888Z" fill="#92AAFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 3C14.6421 3 18 6.35786 18 10.5C18 14.6421 14.6421 18 10.5 18C9.30293 18 8.17142 17.7195 7.16748 17.2207L6.96826 17.1182C7.05373 17.1639 7.14086 17.2068 7.22827 17.2493C5.71427 16.514 4.48508 15.2851 3.75 13.771C3.791 13.8554 3.83272 13.9395 3.87671 14.0222C3.31707 12.9721 3 11.7731 3 10.5C3 6.35786 6.35786 3 10.5 3ZM6.75 10.5C6.75 12.571 8.42894 14.25 10.5 14.25C12.571 14.25 14.25 12.571 14.25 10.5H12.75C12.75 11.7427 11.7427 12.75 10.5 12.75C9.25733 12.75 8.25 11.7427 8.25 10.5H6.75Z" fill="url(#paint0_linear_378_8912)"/>
<path d="M6.75 10.5C6.75 12.571 8.42894 14.25 10.5 14.25C12.571 14.25 14.25 12.571 14.25 10.5H12.75C12.75 11.7427 11.7427 12.75 10.5 12.75C9.25733 12.75 8.25 11.7427 8.25 10.5H6.75Z" fill="url(#paint1_linear_378_8912)"/>
<path d="M10.7599 13.5598C9.4234 13.5598 8.30878 12.6098 8.05442 11.3483C7.99416 11.0494 8.24706 10.7998 8.55191 10.7998C8.85677 10.7998 9.0949 11.0537 9.19463 11.3418C9.41911 11.9902 10.0351 12.4558 10.7599 12.4558C11.4847 12.4558 12.1007 11.9902 12.3251 11.3418C12.4249 11.0537 12.663 10.7998 12.9679 10.7998C13.2727 10.7998 13.5256 11.0494 13.4653 11.3483C13.211 12.6098 12.0964 13.5598 10.7599 13.5598Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_378_8912" x1="12.75" y1="3.375" x2="6.375" y2="16.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#7E9EFF"/>
<stop offset="1" stop-color="#416BFF"/>
</linearGradient>
<linearGradient id="paint1_linear_378_8912" x1="12.75" y1="3.375" x2="6.375" y2="16.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#7E9EFF"/>
<stop offset="1" stop-color="#416BFF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -0,0 +1,19 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.6665 14.1667H13.3332" stroke="#141B34" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.6665 10.8333H9.99984" stroke="#141B34" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.8335 2.08341V2.50008C10.8335 4.85711 10.8335 6.03561 11.5657 6.76785C12.298 7.50008 13.4765 7.50008 15.8335 7.50008H16.2502M16.6668 8.88083V11.6667C16.6668 13.3334 16.6668 14.9048 15.6905 15.8811C14.7142 16.8574 13.1428 16.8574 10.0002 16.8574C6.85746 16.8574 5.28612 16.8574 4.3098 15.8811C3.3335 14.9048 3.3335 13.3334 3.3335 11.6667V7.87995C3.3335 5.17576 3.3335 3.82367 4.07189 2.90786C4.22106 2.72284 4.38959 2.55431 4.5746 2.40514C5.49042 1.66675 6.84251 1.66675 9.54666 1.66675C10.1347 1.66675 10.4286 1.66675 10.6978 1.76176C10.7538 1.78151 10.8087 1.80425 10.8622 1.82987C11.1198 1.95304 11.3277 2.16091 11.7434 2.57665L15.6905 6.52377C16.1722 7.00549 16.4131 7.24635 16.54 7.55263C16.6668 7.85891 16.6668 8.19954 16.6668 8.88083Z" fill="url(#paint0_linear_378_8920)"/>
<path d="M16.6668 8.88083V11.6667C16.6668 13.3334 16.6668 14.9048 15.6905 15.8811C14.7142 16.8574 13.1428 16.8574 10.0002 16.8574C6.85746 16.8574 5.28612 16.8574 4.3098 15.8811C3.3335 14.9048 3.3335 13.3334 3.3335 11.6667V7.87995C3.3335 5.17576 3.3335 3.82367 4.07189 2.90786C4.22106 2.72284 4.38959 2.55431 4.5746 2.40514C5.49042 1.66675 6.84251 1.66675 9.54666 1.66675C10.1347 1.66675 10.4286 1.66675 10.6978 1.76176C10.7538 1.78151 10.8087 1.80425 10.8622 1.82987C11.1198 1.95304 11.3277 2.16091 11.7434 2.57665L15.6905 6.52377C16.1722 7.00549 16.4131 7.24635 16.54 7.55263C16.6668 7.85891 16.6668 8.19954 16.6668 8.88083Z" fill="url(#paint1_linear_378_8920)"/>
<path d="M10.9598 2.82158V2.40491L10.8241 2.08325C10.8801 2.10301 10.935 2.12574 10.9886 2.15137C11.246 2.27449 11.4538 2.48225 11.8693 2.89768L11.8697 2.89814L15.8168 6.84527C16.2986 7.32699 16.5394 7.56784 16.6663 7.87413L16.3765 7.82158H15.9598C13.6028 7.82158 12.4243 7.82158 11.6921 7.08934C10.9598 6.35711 10.9598 5.1786 10.9598 2.82158Z" fill="#BFD6FF"/>
<path d="M8.33301 10H11.6663" stroke="white" stroke-width="1.25" stroke-linecap="round"/>
<path d="M10 8.33325V11.6666" stroke="white" stroke-width="1.25" stroke-linecap="round"/>
<defs>
<linearGradient id="paint0_linear_378_8920" x1="5.00016" y1="1.66675" x2="13.7502" y2="18.3334" gradientUnits="userSpaceOnUse">
<stop stop-color="#5E9EFF"/>
<stop offset="1" stop-color="#5EB1FF"/>
</linearGradient>
<linearGradient id="paint1_linear_378_8920" x1="5.00016" y1="1.66675" x2="13.7502" y2="18.3334" gradientUnits="userSpaceOnUse">
<stop stop-color="#5E9EFF"/>
<stop offset="1" stop-color="#5EB1FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1 @@
<!-- 这里应该是聊天图标的PNG文件 -->

View File

@@ -0,0 +1 @@
<!-- 这里应该是聊天图标激活状态的PNG文件 -->

View File

@@ -0,0 +1 @@
<!-- 这里应该是工单图标的PNG文件 -->

View File

@@ -0,0 +1 @@
<!-- 这里应该是工单图标激活状态的PNG文件 -->

View File

@@ -0,0 +1 @@
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch: function () {\n uni.__f__('log', 'at App.uvue:7', 'App Launch');\n },\n onShow: function () {\n uni.__f__('log', 'at App.uvue:10', 'App Show');\n },\n onHide: function () {\n uni.__f__('log', 'at App.uvue:13', 'App Hide');\n },\n onExit: function () {\n uni.__f__('log', 'at App.uvue:34', 'App Exit');\n },\n});\n//# sourceMappingURL=F:/Project/urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__f__"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ,EAAE;QACT,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;IAC9C,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IAmBD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;CACD,EAAA\"}"}

View File

@@ -0,0 +1 @@
{"code":"import '\u0000plugin-vue:export-helper';\nimport 'uni-mp-runtime';\nimport './pages-json-js';\nimport App from './App.uvue';\nimport { createSSRApp } from 'vue';\nexport function createApp() {\n const app = createSSRApp(App);\n return {\n app\n };\n}\n;\ncreateApp().app.mount(\"#app\");\n//# sourceMappingURL=F:/Project/urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/main.uts.js.map","references":["F:/Project/urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/App.uvue.ts","F:/IDE/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"main.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"main.uts\"],\"names\":[],\"mappings\":\"AAAA,OAAO,2BAA2B,CAAC;AAAA,OAAO,gBAAgB,CAAC;AAAA,OAAO,iBAAiB,CAAC;AAAA,OAAO,GAAG,MAAM,YAAY,CAAA;AAEhH,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAA;AAClC,MAAM,UAAU,SAAS;IACxB,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC7B,OAAO;QACN,GAAG;KACH,CAAA;AACF,CAAC;AAAA,CAAC;AACF,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC\"}"}

View File

@@ -0,0 +1 @@
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch: function () {\n uni.__f__('log', 'at App.uvue:7', 'App Launch');\n },\n onShow: function () {\n uni.__f__('log', 'at App.uvue:10', 'App Show');\n },\n onHide: function () {\n uni.__f__('log', 'at App.uvue:13', 'App Hide');\n },\n onExit: function () {\n uni.__f__('log', 'at App.uvue:34', 'App Exit');\n },\n});\n//# sourceMappingURL=F:/Project/urbanLifeline/urbanLifelineWeb/packages/workcase_wechat/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__f__"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ,EAAE;QACT,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;IAC9C,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IACD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;IAmBD,MAAM,EAAE;QACP,GAAG,CAAC,KAAK,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC7C,CAAC;CACD,EAAA\"}"}