tmp
This commit is contained in:
20
app.js
20
app.js
@@ -1,6 +1,7 @@
|
||||
// app.js
|
||||
import config from './config';
|
||||
import createBus from './utils/eventBus';
|
||||
const API = require('./utils/api.js');
|
||||
|
||||
App({
|
||||
onLaunch() {
|
||||
@@ -26,6 +27,25 @@ App({
|
||||
this.connect();
|
||||
},
|
||||
|
||||
onShow(options) {
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
API.user.wxSignin({ code: res.code })
|
||||
.then((data) => {
|
||||
if (data.code == 0) {
|
||||
this.globalData.user = data.data.user
|
||||
this.eventBus.emit('user-login', data.data.user)
|
||||
} else {
|
||||
console.log('登录失败!')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 全局事件总线 */
|
||||
eventBus: createBus(),
|
||||
|
||||
|
||||
6
app.json
6
app.json
@@ -12,15 +12,15 @@
|
||||
"subpackages": [],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#1a1f3c",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTitleText": "Weixin",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#0f1535"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#8a8a8a",
|
||||
"selectedColor": "#1296db",
|
||||
"backgroundColor": "#0f1535",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
|
||||
13
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-code/chat-markdown-code.d.ts
vendored
Normal file
13
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-code/chat-markdown-code.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { SuperComponent, ComponentsOptionsType } from '../../../../components/common/src/index';
|
||||
export default class ChatMarkdownCode extends SuperComponent {
|
||||
options: ComponentsOptionsType;
|
||||
properties: {
|
||||
node: {
|
||||
type: ObjectConstructor;
|
||||
value: () => {};
|
||||
};
|
||||
};
|
||||
data: {
|
||||
classPrefix: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../../common/src/index";import config from"../../common/config";const{prefix:prefix}=config,name=`${prefix}-chat-markdown-code`;let ChatMarkdownCode=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties={node:{type:Object,value:()=>({})}},this.data={classPrefix:name}}};ChatMarkdownCode=__decorate([wxComponent()],ChatMarkdownCode);export default ChatMarkdownCode;
|
||||
@@ -0,0 +1 @@
|
||||
{"component":true,"styleIsolation":"apply-shared","usingComponents":{}}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="{{classPrefix}}"><view class="{{classPrefix}}__header" wx:if="{{node.lang}}"><text class="{{classPrefix}}__lang">{{node.lang}}</text></view><scroll-view class="{{classPrefix}}__content" scroll-x="{{true}}"><text class="{{classPrefix}}__text" decode="{{true}}">{{node.text}}</text></scroll-view></view>
|
||||
@@ -0,0 +1,7 @@
|
||||
@import '../../common/style/index.wxss';.t-chat-markdown-code{margin:16rpx 0;border-radius:8rpx;background-color:#f6f8fa;border:1rpx solid #e1e4e8;overflow:hidden;}
|
||||
.t-chat-markdown-code__header{padding:8rpx 16rpx;background-color:#e1e4e8;border-bottom:1rpx solid #d0d7de;}
|
||||
.t-chat-markdown-code__lang{font-size:24rpx;color:#656d76;font-weight:500;}
|
||||
.t-chat-markdown-code__content{padding:16rpx;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;white-space:nowrap;height:auto;}
|
||||
.t-chat-markdown-code__text{font:var(--td-font-body-medium,28rpx / 44rpx var(--td-font-family,PingFang SC,Microsoft YaHei,Arial Regular));font-family:'SF Mono',Monaco,'Cascadia Code','Roboto Mono',Consolas,'Courier New',monospace;color:#24292f;white-space:pre;word-wrap:normal;word-break:normal;overflow-wrap:normal;display:inline-block;vertical-align:top;min-width:100%;}
|
||||
.t-chat-markdown-code-light .t-chat-markdown-code-block{background-color:#fff;border-color:#d0d7de;}
|
||||
.t-chat-markdown-code-light .t-chat-markdown-code-header{background-color:#f6f8fa;border-bottom-color:#d0d7de;}
|
||||
23
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-node/chat-markdown-node.d.ts
vendored
Normal file
23
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-node/chat-markdown-node.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SuperComponent, ComponentsOptionsType } from '../../../../components/common/src/index';
|
||||
export default class ChatMarkdownNode extends SuperComponent {
|
||||
options: ComponentsOptionsType;
|
||||
properties: {
|
||||
nodes: {
|
||||
type: ArrayConstructor;
|
||||
value: () => any[];
|
||||
};
|
||||
};
|
||||
data: {
|
||||
classPrefix: string;
|
||||
};
|
||||
methods: {
|
||||
linkClick(e: any): void;
|
||||
getCareMarkdown(): any;
|
||||
handleClick(event: any, type: any, token: any): void;
|
||||
};
|
||||
lifetimes: {
|
||||
created(): void;
|
||||
attached(): void;
|
||||
detached(): void;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../../common/src/index";import config from"../../common/config";const{prefix:prefix}=config,name=`${prefix}-chat-markdown`;let ChatMarkdownNode=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties={nodes:{type:Array,value:()=>[]}},this.data={classPrefix:name},this.methods={linkClick(t){var e;const{index:a}=t.currentTarget.dataset||{},r=null===(e=this.data.nodes)||void 0===e?void 0:e[a];this.handleClick(t,"link-tap",r)},getCareMarkdown(){if(this.data.careMarkdown)return this.data.careMarkdown;for(this.setData({careMarkdown:this.selectOwnerComponent()});this.data.careMarkdown.__data__.name!==name;this.setData({careMarkdown:this.data.careMarkdown.selectOwnerComponent()}));return this.data.careMarkdown},handleClick(t,e,a){this.data.getCareMarkdown().triggerEvent("click",{event:t,node:a})}},this.lifetimes={created(){this.data.getCareMarkdown=this.getCareMarkdown.bind(this),this.data.handleClick=this.handleClick.bind(this)},attached(){},detached(){}}}};ChatMarkdownNode=__decorate([wxComponent()],ChatMarkdownNode);export default ChatMarkdownNode;
|
||||
@@ -0,0 +1 @@
|
||||
{"component":true,"styleIsolation":"apply-shared","usingComponents":{"chat-markdown-table":"../chat-markdown-table/chat-markdown-table","chat-markdown-code":"../chat-markdown-code/chat-markdown-code","chat-markdown-node":"./chat-markdown-node"}}
|
||||
@@ -0,0 +1 @@
|
||||
<block wx:for="{{nodes}}" wx:for-item="item" wx:for-index="i" wx:key="i"><block wx:if="{{item.type==='heading'}}"><view class="{{classPrefix}}-h {{ classPrefix}}-h{{item.depth}}"><chat-markdown-node nodes="{{item.tokens}}"/></view></block><block wx:elif="{{item.type==='list'}}"><view class="{{classPrefix}}-list {{item.ordered ? classPrefix + '-list__decimal' : ''}}" data-type="{{item.ordered}}"><block wx:for="{{item.items}}" wx:for-item="li" wx:for-index="j" wx:key="j"><view class="{{classPrefix}}-list-item"><block wx:if="{{li.tokens && li.tokens.length}}"><chat-markdown-node nodes="{{li.tokens}}"/></block><block wx:else>{{''+li.text+''}}</block></view></block></view></block><block wx:elif="{{item.type==='paragraph'}}"><view class="{{classPrefix}}-p"><chat-markdown-node nodes="{{item.tokens}}"/></view></block><block wx:elif="{{item.type==='image'}}"><view class="{{classPrefix}}-image"><image src="{{item.href}}" alt="{{item.title}}" mode="widthFix"/></view></block><block wx:elif="{{item.type==='table'}}"><chat-markdown-table node="{{item}}"/></block><block wx:elif="{{item.type==='blockquote'}}"><view class="{{classPrefix}}-blockquote"><chat-markdown-node nodes="{{item.tokens}}"/></view></block><block wx:elif="{{item.type==='code'}}"><chat-markdown-code node="{{item}}" theme="{{theme}}"/></block><block wx:elif="{{item.type==='text'}}"><view class="{{classPrefix}}-text {{classPrefix}}-inline" data-raw="{{item.raw}}"><block wx:if="{{item.tokens && item.tokens.length}}"><chat-markdown-node nodes="{{item.tokens}}"/></block><block wx:else>{{''+item.raw+''}}</block></view></block><block wx:elif="{{item.type==='strong'}}"><view class="{{classPrefix}}-strong {{classPrefix}}-inline"><block wx:if="{{item.tokens && item.tokens.length}}"><chat-markdown-node nodes="{{item.tokens}}"/></block><block wx:else>{{''+item.text+''}}</block></view></block><block wx:elif="{{item.type==='em'}}"><view class="{{classPrefix}}-em {{classPrefix}}-inline"><block wx:if="{{item.tokens && item.tokens.length}}"><chat-markdown-node nodes="{{item.tokens}}"/></block><block wx:else>{{''+item.text+''}}</block></view></block><block wx:elif="{{item.type==='del'}}"><view class="{{classPrefix}}-del {{classPrefix}}-inline"><block wx:if="{{item.tokens && item.tokens.length}}"><chat-markdown-node nodes="{{item.tokens}}"/></block><block wx:else>{{''+item.text+''}}</block></view></block><block wx:elif="{{item.type==='link'}}"><view class="{{classPrefix}}-link {{classPrefix}}-inline" data-index="{{i}}" bindtap="linkClick"><block wx:if="{{item.tokens && item.tokens.length}}"><chat-markdown-node nodes="{{item.tokens}}"/></block></view></block><block wx:elif="{{item.type==='ref'}}"><view class="{{classPrefix}}-ref {{classPrefix}}-inline"><text class="{{classPrefix}}-ref-txt">{{''+item.text+''}}</text></view></block><block wx:elif="{{item.type==='space'}}"><view class="{{classPrefix}}-space"></view></block><block wx:elif="{{item.type==='br'}}"><view class="{{classPrefix}}-br"></view></block><block wx:elif="{{item.type==='hr'}}"><view class="{{classPrefix}}-hr"></view></block><block wx:elif="{{item.type==='codespan'}}"><view class="{{classPrefix}}-codespan {{classPrefix}}-inline" data-type="{{item.type}}">{{''+(item.text||item.raw)+''}}</view></block><block wx:else><view class="{{classPrefix}}-raw {{classPrefix}}-inline" data-type="{{item.type}}">{{''+(item.text||item.raw)+''}}</view></block></block>
|
||||
@@ -0,0 +1 @@
|
||||
@import '../../common/style/index.wxss';
|
||||
13
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-table/chat-markdown-table.d.ts
vendored
Normal file
13
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown-table/chat-markdown-table.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { SuperComponent, ComponentsOptionsType } from '../../../../components/common/src/index';
|
||||
export default class ChatMarkdownTable extends SuperComponent {
|
||||
options: ComponentsOptionsType;
|
||||
properties: {
|
||||
node: {
|
||||
type: ObjectConstructor;
|
||||
value: {};
|
||||
};
|
||||
};
|
||||
data: {
|
||||
classPrefix: string;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../../common/src/index";import config from"../../common/config";const{prefix:prefix}=config,name=`${prefix}-chat-markdown-table`;let ChatMarkdownTable=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties={node:{type:Object,value:{}}},this.data={classPrefix:name}}};ChatMarkdownTable=__decorate([wxComponent()],ChatMarkdownTable);export default ChatMarkdownTable;
|
||||
@@ -0,0 +1 @@
|
||||
{"component":true,"styleIsolation":"apply-shared","usingComponents":{"chat-markdown-node":"../chat-markdown-node/chat-markdown-node"}}
|
||||
@@ -0,0 +1 @@
|
||||
<view class="{{classPrefix}}"><view class="{{classPrefix}}__container"><view class="{{classPrefix}}__thead"><view class="{{classPrefix}}__tr"><block wx:for="{{node.header}}" wx:for-item="th" wx:for-index="j" wx:key="j"><view class="{{classPrefix}}__th" style="{{('text-align:'+node.align[j]||'left'+';')}}"><chat-markdown-node nodes="{{th.tokens}}"></chat-markdown-node></view></block></view></view><view class="{{classPrefix}}__tbody"><block wx:for="{{node.rows}}" wx:for-item="row" wx:for-index="k" wx:key="k"><view class="{{classPrefix}}__tr"><block wx:for="{{row}}" wx:for-item="cell" wx:for-index="l" wx:key="l"><view class="{{classPrefix}}__td" style="{{('text-align:'+node.align[l]||'left'+';')}}"><chat-markdown-node nodes="{{cell.tokens}}"></chat-markdown-node></view></block></view></block></view></view></view>
|
||||
@@ -0,0 +1,12 @@
|
||||
@import '../../common/style/index.wxss';.t-chat-markdown-table{width:100%;overflow-y:hidden;overflow-x:auto;border:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown-table__container{display:table;min-width:100%;max-width:max-content;border-collapse:collapse;white-space:nowrap;}
|
||||
.t-chat-markdown-table__thead{display:table-header-group;}
|
||||
.t-chat-markdown-table__tbody{display:table-row-group;}
|
||||
.t-chat-markdown-table__tr{display:table-row;border-bottom:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown-table__tr:last-child{border-bottom:none;}
|
||||
.t-chat-markdown-table__tr:nth-child(2n+1){background-color:var(--td-bg-color-container,var(--td-font-white-1,#fff));}
|
||||
.t-chat-markdown-table__tr:nth-child(2n){background-color:var(--td-bg-color-secondarycontainer,var(--td-gray-color-1,#f3f3f3));}
|
||||
.t-chat-markdown-table__th{display:table-cell;vertical-align:middle;background-color:var(--td-bg-color-secondarycontainer,var(--td-gray-color-1,#f3f3f3));padding:5rpx 10rpx;color:var(--td-text-color-secondary,var(--td-font-gray-2,rgba(0,0,0,.6)));border-right:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown-table__th:last-child{border-right:none;}
|
||||
.t-chat-markdown-table__td{display:table-cell;vertical-align:middle;padding:5rpx 10rpx;border-right:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown-table__td:last-child{border-right:none;}
|
||||
22
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown.d.ts
vendored
Normal file
22
miniprogram_npm/tdesign-miniprogram/chat-markdown/chat-markdown.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { SuperComponent, ComponentsOptionsType } from '../../../components/common/src/index';
|
||||
import { TdChatMarkdownProps } from './type';
|
||||
export interface ChatMarkdownProps extends TdChatMarkdownProps {
|
||||
}
|
||||
export default class ChatMarkdown extends SuperComponent {
|
||||
options: ComponentsOptionsType;
|
||||
properties: TdChatMarkdownProps;
|
||||
data: {
|
||||
classPrefix: string;
|
||||
nodes: any[];
|
||||
name: string;
|
||||
};
|
||||
observers: {
|
||||
content: (markdown: string) => void;
|
||||
};
|
||||
methods: {
|
||||
parseMarkdown(markdown: string): void;
|
||||
};
|
||||
lifetimes: {
|
||||
attached(): void;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import{__decorate}from"tslib";import{Lexer}from"marked";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";const{prefix:prefix}=config,name=`${prefix}-chat-markdown`;let ChatMarkdown=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties=props,this.data={classPrefix:name,nodes:[],name:name},this.observers={content:function(o){this.parseMarkdown(o)}},this.methods={parseMarkdown(o){try{const t=new Lexer(this.data.options).lex(o);this.setData({nodes:t})}catch(t){console.error("Markdown parsing error:",t),this.setData({nodes:[{type:"text",raw:o,text:o}]})}}},this.lifetimes={attached(){}}}};ChatMarkdown=__decorate([wxComponent()],ChatMarkdown);export default ChatMarkdown;
|
||||
@@ -0,0 +1 @@
|
||||
{"component":true,"styleIsolation":"shared","usingComponents":{"chat-markdown-node":"./chat-markdown-node/chat-markdown-node"}}
|
||||
@@ -0,0 +1 @@
|
||||
<wxs src="../common/utils.wxs" module="_"/><view class="class {{classPrefix}} {{classPrefix}}--normal" style="{{_._style([style, customStyle])}}"><chat-markdown-node nodes="{{nodes}}"></chat-markdown-node></view>
|
||||
@@ -0,0 +1,28 @@
|
||||
@import '../common/style/index.wxss';.t-chat-markdown{color:var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9)));word-wrap:break-word;word-break:break-word;line-height:1.75;}
|
||||
.t-chat-markdown-inline{display:inline;}
|
||||
.t-chat-markdown-p{-webkit-margin-before:var(--td-spacer-1,24rpx);margin-block-start:var(--td-spacer-1,24rpx);-webkit-margin-after:var(--td-spacer-1,24rpx);margin-block-end:var(--td-spacer-1,24rpx);}
|
||||
.t-chat-markdown-p:first-child{-webkit-margin-before:0;margin-block-start:0;}
|
||||
.t-chat-markdown-p:last-child{-webkit-margin-after:0;margin-block-end:0;}
|
||||
.t-chat-markdown-blockquote{padding:0 .75em;color:var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9)));background-color:var(--td-bg-color-secondarycontainer,var(--td-gray-color-1,#f3f3f3));border-left:4rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));margin-bottom:var(--td-spacer-1,24rpx);}
|
||||
.t-chat-markdown-h{font-size:1em;margin:var(--td-spacer-1,24rpx) 0;font-weight:700;}
|
||||
.t-chat-markdown-h1{font-size:2em;}
|
||||
.t-chat-markdown-h2{font-size:1.75em;}
|
||||
.t-chat-markdown-h3{font-size:1.5em;}
|
||||
.t-chat-markdown-h4{font-size:1.25em;}
|
||||
.t-chat-markdown-h5{font-size:1em;}
|
||||
.t-chat-markdown-h6{font-size:.75em;}
|
||||
.t-chat-markdown-em{font-style:italic;}
|
||||
.t-chat-markdown-strong{font-weight:700;}
|
||||
.t-chat-markdown-hr{height:6rpx;padding:0;margin:var(--td-spacer-1,24rpx) 0;background-color:var(--td-component-border,var(--td-gray-color-4,#dcdcdc));border:0;}
|
||||
.t-chat-markdown-list{display:block;padding:0;margin:0 0 var(--td-spacer,16rpx) 1.5em;}
|
||||
.t-chat-markdown-list__decimal{list-style-type:decimal;}
|
||||
.t-chat-markdown-list-item{display:list-item;margin-bottom:var(--td-spacer-1,24rpx);}
|
||||
.t-chat-markdown-link{color:var(--td-brand-color,var(--td-primary-color-7,#0052d9));}
|
||||
.t-chat-markdown-del{text-decoration:line-through;}
|
||||
.t-chat-markdown-codespan{padding:4rpx 8rpx;margin:0 4rpx;border-radius:8rpx;font-size:.8em;overflow-x:auto;background-color:var(--td-bg-color-page,var(--td-gray-color-1,#f3f3f3));border:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown .t-chat-markdown-table__container{display:table;width:100%;border-collapse:collapse;}
|
||||
.t-chat-markdown .t-chat-markdown-table__container-thead{display:table-header-group;}
|
||||
.t-chat-markdown .t-chat-markdown-table__container-tbody{display:table-row-group;}
|
||||
.t-chat-markdown .t-chat-markdown-table__container-tr{display:table-row;}
|
||||
.t-chat-markdown .t-chat-markdown-table__container-th{display:table-cell;vertical-align:middle;background-color:var(--td-bg-color-component,var(--td-gray-color-3,#e7e7e7));font-weight:700;padding:5rpx 10rpx;border:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
.t-chat-markdown .t-chat-markdown-table__container-td{display:table-cell;vertical-align:middle;padding:5rpx 10rpx;border:1rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));}
|
||||
3
miniprogram_npm/tdesign-miniprogram/chat-markdown/index.d.ts
vendored
Normal file
3
miniprogram_npm/tdesign-miniprogram/chat-markdown/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './props';
|
||||
export * from './type';
|
||||
export * from './chat-markdown';
|
||||
@@ -0,0 +1 @@
|
||||
export*from"./props";export*from"./type";export*from"./chat-markdown";
|
||||
3
miniprogram_npm/tdesign-miniprogram/chat-markdown/props.d.ts
vendored
Normal file
3
miniprogram_npm/tdesign-miniprogram/chat-markdown/props.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { TdChatMarkdownProps } from './type';
|
||||
declare const props: TdChatMarkdownProps;
|
||||
export default props;
|
||||
@@ -0,0 +1 @@
|
||||
const props={content:{type:String,value:"",required:!0},options:{type:Object,value:{gfm:!0,pedantic:!1,breaks:!0}}};export default props;
|
||||
17
miniprogram_npm/tdesign-miniprogram/chat-markdown/type.d.ts
vendored
Normal file
17
miniprogram_npm/tdesign-miniprogram/chat-markdown/type.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface TdChatMarkdownProps {
|
||||
content: {
|
||||
type: StringConstructor;
|
||||
value?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
options?: {
|
||||
type: ObjectConstructor;
|
||||
value?: TdChatContentMDOptions;
|
||||
};
|
||||
}
|
||||
export interface TdChatContentMDOptions {
|
||||
gfm?: boolean;
|
||||
pedantic?: boolean;
|
||||
smartLists?: boolean;
|
||||
breaks?: boolean;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export{};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,66 +1,237 @@
|
||||
// pages/ai/index.js
|
||||
const AIChatSocket = require('../../utils/chatmsg.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
messages: [],
|
||||
inputValue: '',
|
||||
isTyping: false,
|
||||
scrollToMessage: '',
|
||||
quickQuestions: [
|
||||
'今日待处理订单有哪些?',
|
||||
'最近客户反馈统计',
|
||||
'如何修改服务价格?',
|
||||
'陪诊员排班情况'
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
messageIdCounter: 0,
|
||||
socket: null,
|
||||
|
||||
onLoad() {
|
||||
this.loadChatHistory()
|
||||
this.initSocket()
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.close()
|
||||
this.socket = null
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
initSocket() {
|
||||
this.socket = new AIChatSocket()
|
||||
|
||||
this.socket.onOpen(() => {
|
||||
console.log('[AIChat] Socket opened')
|
||||
})
|
||||
|
||||
this.socket.onMessage((data) => {
|
||||
if (data && data.type === 'ai') {
|
||||
this.handleAIReply(data)
|
||||
} else if (data && data.type === 'system') {
|
||||
console.log('[AIChat] System:', data.content)
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.onError((err) => {
|
||||
console.error('[AIChat] WebSocket error', err)
|
||||
this.handleAIError('网络连接失败,请检查网络设置')
|
||||
})
|
||||
|
||||
this.socket.onClose((res) => {
|
||||
console.log('[AIChat] Socket closed', res)
|
||||
})
|
||||
|
||||
this.socket.connect().catch((err) => {
|
||||
console.error('[AIChat] Connect failed', err)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
handleAIReply(data) {
|
||||
let messages = null
|
||||
if (this.data.messages.length > 0) {
|
||||
let lastMessage = this.data.messages[this.data.messages.length - 1]
|
||||
if (lastMessage.type === 'ai' && lastMessage._serverId === data.id) {
|
||||
lastMessage.content += data.content || ''
|
||||
messages = this.data.messages
|
||||
}
|
||||
}
|
||||
|
||||
if (!messages) {
|
||||
const aiResponse = {
|
||||
id: `msg_${++this.messageIdCounter}`,
|
||||
_serverId: data.id,
|
||||
type: 'ai',
|
||||
contentType: 'text',
|
||||
content: data.content || '',
|
||||
time: this.formatTime(new Date())
|
||||
}
|
||||
|
||||
if (data.sessionId) {
|
||||
wx.setStorageSync('admin_ai_session_id', data.sessionId)
|
||||
}
|
||||
|
||||
messages = [...this.data.messages, aiResponse]
|
||||
}
|
||||
|
||||
this.setData({
|
||||
messages: messages,
|
||||
isTyping: false
|
||||
})
|
||||
this.saveChatHistory(messages)
|
||||
this.scrollToBottom()
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
loadChatHistory() {
|
||||
try {
|
||||
const history = wx.getStorageSync('admin_ai_chat_history')
|
||||
if (history && history.length > 0) {
|
||||
this.setData({ messages: history })
|
||||
this.scrollToBottom()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('加载聊天记录失败', e)
|
||||
}
|
||||
},
|
||||
|
||||
saveChatHistory(messages) {
|
||||
try {
|
||||
wx.setStorageSync('admin_ai_chat_history', messages)
|
||||
} catch (e) {
|
||||
console.log('保存聊天记录失败', e)
|
||||
}
|
||||
},
|
||||
|
||||
onInputChange(e) {
|
||||
const value = e.detail.value
|
||||
this.setData({
|
||||
inputValue: value,
|
||||
canSend: !!value.trim()
|
||||
})
|
||||
},
|
||||
|
||||
onQuickQuestionTap(e) {
|
||||
const question = e.currentTarget.dataset.question
|
||||
this.setData({ inputValue: question, canSend: true })
|
||||
this.sendMessage()
|
||||
},
|
||||
|
||||
sendMessage() {
|
||||
const content = this.data.inputValue.trim()
|
||||
if (!content) return
|
||||
|
||||
const userMessage = {
|
||||
id: `msg_${++this.messageIdCounter}`,
|
||||
type: 'user',
|
||||
contentType: 'text',
|
||||
content: content,
|
||||
time: this.formatTime(new Date())
|
||||
}
|
||||
|
||||
const messages = [...this.data.messages, userMessage]
|
||||
this.setData({
|
||||
messages: messages,
|
||||
inputValue: '',
|
||||
canSend: false,
|
||||
isTyping: true
|
||||
})
|
||||
|
||||
this.saveChatHistory(messages)
|
||||
this.scrollToBottom()
|
||||
|
||||
this.sendToAI(content)
|
||||
},
|
||||
|
||||
sendToAI(content, type = 'chat') {
|
||||
const app = getApp()
|
||||
const user = app.globalData.user
|
||||
|
||||
if (!this.socket) {
|
||||
this.handleAIError('网络连接失败,请检查网络设置')
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.socket.isConnected) {
|
||||
this.socket.connect().then(() => {
|
||||
this._doSend(type, content, user, app)
|
||||
}).catch((err) => {
|
||||
console.error('[AIChat] Reconnect failed', err)
|
||||
this.handleAIError('网络连接失败,请检查网络设置')
|
||||
})
|
||||
} else {
|
||||
this._doSend(type, content, user, app)
|
||||
}
|
||||
},
|
||||
|
||||
_doSend(type, content, user, app) {
|
||||
this.socket.send({
|
||||
type: type,
|
||||
content: content,
|
||||
userId: user ? user._id : '',
|
||||
token: user ? user.security.token : '',
|
||||
appId: app.globalData.appId || '',
|
||||
agent: 'escort-admin'
|
||||
})
|
||||
},
|
||||
|
||||
handleAIError(errorMessage) {
|
||||
const errorResponse = {
|
||||
id: `msg_${++this.messageIdCounter}`,
|
||||
type: 'ai',
|
||||
contentType: 'text',
|
||||
content: errorMessage,
|
||||
time: this.formatTime(new Date())
|
||||
}
|
||||
|
||||
const messages = [...this.data.messages, errorResponse]
|
||||
this.setData({
|
||||
messages: messages,
|
||||
isTyping: false
|
||||
})
|
||||
this.saveChatHistory(messages)
|
||||
this.scrollToBottom()
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
setTimeout(() => {
|
||||
const lastMessage = this.data.messages[this.data.messages.length - 1]
|
||||
if (lastMessage) {
|
||||
this.setData({
|
||||
scrollToMessage: `msg-${lastMessage.id}`
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
|
||||
formatTime(date) {
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${hours}:${minutes}`
|
||||
},
|
||||
|
||||
clearChat() {
|
||||
wx.showModal({
|
||||
title: '确认清空',
|
||||
content: '确定要清空所有聊天记录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.sendToAI('clear', 'clear')
|
||||
this.setData({ messages: [] })
|
||||
wx.removeStorageSync('admin_ai_chat_history')
|
||||
wx.removeStorageSync('admin_ai_session_id')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
"navigationBarTitleText": "AI助手",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"t-chat-markdown": "tdesign-miniprogram/chat-markdown/chat-markdown"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,66 @@
|
||||
<!--pages/ai/index.wxml-->
|
||||
<text>pages/ai/index.wxml</text>
|
||||
<view class="chat-container">
|
||||
<scroll-view class="message-list" scroll-y scroll-into-view="{{scrollToMessage}}" scroll-with-animation>
|
||||
<view class="quick-questions" wx:if="{{messages.length === 0}}">
|
||||
<view class="welcome-text">您好,我是您的AI助手,有什么可以帮您?</view>
|
||||
<view class="quick-list">
|
||||
<view class="quick-item" wx:for="{{quickQuestions}}" wx:key="*this" data-question="{{item}}" bindtap="onQuickQuestionTap">
|
||||
{{item}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="message-item {{item.type}}" wx:for="{{messages}}" wx:key="id" id="msg-{{item.id}}">
|
||||
<view class="avatar {{item.type}}-avatar">
|
||||
<text class="avatar-text">{{item.type === 'user' ? '我' : 'AI'}}</text>
|
||||
</view>
|
||||
<view class="message-content">
|
||||
<view class="message-bubble {{item.type}}-bubble">
|
||||
<t-chat-markdown
|
||||
wx:if="{{item.contentType === 'text' && item.type === 'ai'}}"
|
||||
class="message-markdown"
|
||||
content="{{item.content}}"
|
||||
/>
|
||||
<text class="message-text" wx:elif="{{item.contentType === 'text'}}">{{item.content}}</text>
|
||||
</view>
|
||||
<text class="message-time" wx:if="{{item.time}}">{{item.time}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="typing-indicator" wx:if="{{isTyping}}">
|
||||
<view class="avatar ai-avatar">
|
||||
<text class="avatar-text">AI</text>
|
||||
</view>
|
||||
<view class="typing-bubble">
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="chat-footer">
|
||||
<view class="chat-toolbar" wx:if="{{messages.length > 0}}">
|
||||
<view class="chat-toolbar-action" bindtap="clearChat">
|
||||
<text>清空记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-area">
|
||||
<input
|
||||
class="chat-input"
|
||||
type="text"
|
||||
placeholder="请输入您的问题..."
|
||||
value="{{inputValue}}"
|
||||
bindinput="onInputChange"
|
||||
confirm-type="send"
|
||||
bindconfirm="sendMessage"
|
||||
adjust-position="{{true}}"
|
||||
cursor-spacing="20"
|
||||
hold-keyboard="{{true}}"
|
||||
/>
|
||||
<view class="send-btn {{canSend ? 'active' : ''}}" bindtap="sendMessage">
|
||||
<text class="send-text">发送</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1 +1,367 @@
|
||||
/* pages/ai/index.wxss */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-header-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 200rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-questions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 20rpx;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
font-size: 32rpx;
|
||||
color: #1a1a2e;
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quick-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #4c6ef5;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
text-align: center;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.quick-item:active {
|
||||
transform: scale(0.98);
|
||||
background-color: rgba(76, 110, 245, 0.05);
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.message-item.ai {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message-item.user {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
margin-top: 8rpx;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
.message-item.ai .message-time {
|
||||
text-align: left;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.message-item.user .message-time {
|
||||
text-align: right;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
}
|
||||
|
||||
.ai-avatar {
|
||||
background: linear-gradient(135deg, #20c997, #51cf66);
|
||||
box-shadow: 0 4rpx 12rpx rgba(32, 201, 151, 0.25);
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.message-item.user .message-content {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-item.ai .message-content {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.user-bubble {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.2);
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.ai-bubble {
|
||||
background-color: #ffffff;
|
||||
color: #1a1a2e;
|
||||
border-bottom-left-radius: 4rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid #e5e7eb;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
/* Markdown 渲染区域 */
|
||||
.message-markdown {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 表格样式覆盖(防止过宽) */
|
||||
.message-markdown .t-chat-markdown-table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.message-markdown .t-chat-markdown-table__container {
|
||||
display: table;
|
||||
min-width: 100%;
|
||||
max-width: max-content;
|
||||
border-collapse: collapse;
|
||||
white-space: normal;
|
||||
table-layout: fixed;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.message-markdown .t-chat-markdown-table__th,
|
||||
.message-markdown .t-chat-markdown-table__td {
|
||||
padding: 8rpx 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 320rpx;
|
||||
}
|
||||
|
||||
.message-markdown .t-chat-markdown-table__th {
|
||||
background-color: #f5f6fa;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.message-markdown .t-chat-markdown-table__tr:nth-child(2n) {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
/* 代码块样式覆盖 */
|
||||
.message-markdown .t-chat-markdown-codespan,
|
||||
.message-markdown .t-chat-markdown-code {
|
||||
font-size: 24rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.typing-bubble {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
background-color: #ffffff;
|
||||
padding: 24rpx 32rpx;
|
||||
border-radius: 16rpx;
|
||||
border-bottom-left-radius: 4rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background-color: #9ca3af;
|
||||
border-radius: 50%;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.6);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.input-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: #ffffff;
|
||||
gap: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.chat-toolbar {
|
||||
display: flex;
|
||||
align-items: right;
|
||||
justify-content: right;
|
||||
padding: 12rpx 24rpx;
|
||||
background-color: #f5f6fa;
|
||||
border-top: 1rpx solid #e5e7eb;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.chat-toolbar-action {
|
||||
display: flex;
|
||||
align-items: right;
|
||||
justify-content: right;
|
||||
padding: 6rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.chat-toolbar-action:active {
|
||||
transform: scale(0.96);
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
border-color: #4c6ef5;
|
||||
}
|
||||
|
||||
.chat-toolbar-action text {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
background-color: #f5f6fa;
|
||||
border-radius: 36rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.chat-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
width: 120rpx;
|
||||
height: 72rpx;
|
||||
background-color: #e5e7eb;
|
||||
border-radius: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.send-btn.active {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
}
|
||||
|
||||
.send-text {
|
||||
color: #9ca3af;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.send-btn.active .send-text {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
@@ -1,66 +1,269 @@
|
||||
// pages/customer/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
// 性别映射
|
||||
const SEX_MAP = {
|
||||
male: { label: '男', text: '先生' },
|
||||
female: { label: '女', text: '女士' },
|
||||
other: { label: '其他', text: '' },
|
||||
'': { label: '未知', text: '' }
|
||||
}
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
// 搜索相关
|
||||
searchKey: '',
|
||||
// 客户列表
|
||||
customerList: [],
|
||||
// 分页
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
hasMore: true,
|
||||
// 加载状态
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
isRefreshing: false,
|
||||
// 统计数据
|
||||
stats: {
|
||||
total: 0,
|
||||
male: 0,
|
||||
female: 0
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
this.loadCustomerList();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
// 页面显示时如果已有数据则刷新
|
||||
if (this.data.customerList.length > 0) {
|
||||
this.refreshData();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
* 格式化日期
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
* 处理客户数据
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
processCustomers(customers) {
|
||||
return customers.map(customer => {
|
||||
const profile = customer.profile || {};
|
||||
const sexInfo = SEX_MAP[profile.sex] || SEX_MAP[''];
|
||||
return {
|
||||
...customer,
|
||||
name: profile.name || '未知姓名',
|
||||
mobile: profile.mobile || '暂无电话',
|
||||
sex: profile.sex || '',
|
||||
sexLabel: sexInfo.label,
|
||||
sexText: sexInfo.text,
|
||||
avatar: profile.avatar || '',
|
||||
avatarText: profile.name ? profile.name[0].toUpperCase() : '客',
|
||||
birth: profile.birth ? this.formatDate(profile.birth) : '',
|
||||
createdAt: customer.meta?.createtime ? this.formatDate(customer.meta.createtime) : '',
|
||||
locationText: this.getLocationText(customer)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
* 获取位置文本
|
||||
*/
|
||||
getLocationText(customer) {
|
||||
const location = customer.location || {};
|
||||
if (location.province || location.city) {
|
||||
return `${location.province || ''}${location.city || ''}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算统计数据
|
||||
*/
|
||||
calculateStats(customers) {
|
||||
const stats = {
|
||||
total: customers.length,
|
||||
male: 0,
|
||||
female: 0
|
||||
};
|
||||
|
||||
customers.forEach(customer => {
|
||||
const sex = customer.profile?.sex || '';
|
||||
if (sex === 'male') stats.male++;
|
||||
else if (sex === 'female') stats.female++;
|
||||
});
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载客户列表
|
||||
*/
|
||||
loadCustomerList(isRefresh = false) {
|
||||
if (this.data.isLoading || this.data.isLoadingMore) return;
|
||||
|
||||
const { page, pageSize, searchKey } = this.data;
|
||||
this.setData({
|
||||
isLoading: !isRefresh && page === 1,
|
||||
isLoadingMore: isRefresh && page > 1
|
||||
});
|
||||
|
||||
const params = {
|
||||
page,
|
||||
pageSize
|
||||
};
|
||||
|
||||
if (searchKey && searchKey.trim()) {
|
||||
params.keyword = searchKey.trim();
|
||||
}
|
||||
|
||||
API.user.userList(params)
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '获取客户列表失败', icon: 'none' });
|
||||
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const data = res.data || {};
|
||||
const list = data.users || [];
|
||||
const total = data?.users?.length || 0;
|
||||
|
||||
const processedCustomers = this.processCustomers(list);
|
||||
|
||||
let allCustomers = isRefresh && page > 1
|
||||
? [...this.data.customerList, ...processedCustomers]
|
||||
: processedCustomers;
|
||||
|
||||
// 如果有搜索条件,在前端过滤
|
||||
if (searchKey && searchKey.trim()) {
|
||||
const keyword = searchKey.trim().toLowerCase();
|
||||
allCustomers = allCustomers.filter(customer => {
|
||||
return customer.name.toLowerCase().includes(keyword) ||
|
||||
customer.mobile.includes(keyword);
|
||||
});
|
||||
}
|
||||
|
||||
const stats = this.calculateStats(allCustomers);
|
||||
|
||||
this.setData({
|
||||
customerList: allCustomers,
|
||||
stats,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
isRefreshing: false,
|
||||
hasMore: allCustomers.length < total
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取客户列表失败', err);
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' });
|
||||
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
*/
|
||||
refreshData() {
|
||||
this.setData({ page: 1, hasMore: true }, () => {
|
||||
this.loadCustomerList();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索输入
|
||||
*/
|
||||
onSearchInput(e) {
|
||||
this.setData({ searchKey: e.detail.value });
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索确认
|
||||
*/
|
||||
onSearch() {
|
||||
this.setData({ page: 1, hasMore: true, customerList: [] }, () => {
|
||||
this.loadCustomerList();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除搜索
|
||||
*/
|
||||
onClearSearch() {
|
||||
if (this.data.searchKey) {
|
||||
this.setData({ searchKey: '', page: 1, hasMore: true, customerList: [] }, () => {
|
||||
this.loadCustomerList();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
*/
|
||||
onCustomerDetail(e) {
|
||||
const { id } = e.currentTarget.dataset;
|
||||
wx.navigateTo({
|
||||
url: `/pages/customer/detail?id=${id}`
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 拨打电话
|
||||
*/
|
||||
onCallPhone(e) {
|
||||
const { phone } = e.currentTarget.dataset;
|
||||
if (!phone) {
|
||||
wx.showToast({ title: '暂无电话号码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
wx.makePhoneCall({
|
||||
phoneNumber: phone,
|
||||
fail: () => {
|
||||
wx.showToast({ title: '拨打电话失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
this.setData({ isRefreshing: true, page: 1, hasMore: true }, () => {
|
||||
this.loadCustomerList();
|
||||
});
|
||||
wx.stopPullDownRefresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
* 上拉加载更多
|
||||
*/
|
||||
onReachBottom() {
|
||||
if (!this.data.hasMore || this.data.isLoadingMore) return;
|
||||
|
||||
this.setData({ page: this.data.page + 1 }, () => {
|
||||
this.loadCustomerList(true);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
return {
|
||||
title: '客户管理',
|
||||
path: '/pages/customer/index'
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
"navigationBarTitleText": "客户管理",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,328 @@
|
||||
/* pages/customer/index.wxss */
|
||||
/* pages/customer/index.less */
|
||||
|
||||
// 颜色变量
|
||||
@bg-primary: #f5f6fa;
|
||||
@bg-secondary: #ffffff;
|
||||
@bg-card: #ffffff;
|
||||
@accent-primary: #4c6ef5;
|
||||
@accent-secondary: #6b7aff;
|
||||
@accent-gradient-start: #4c6ef5;
|
||||
@accent-gradient-end: #748ffc;
|
||||
@text-primary: #1a1a2e;
|
||||
@text-secondary: #6b7280;
|
||||
@text-muted: #9ca3af;
|
||||
@border-color: #e5e7eb;
|
||||
@male-color: #4c6ef5;
|
||||
@female-color: #ff6b6b;
|
||||
@divider-color: #f3f4f6;
|
||||
|
||||
page {
|
||||
background-color: @bg-primary;
|
||||
color: @text-primary;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.customer-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: @bg-primary;
|
||||
}
|
||||
|
||||
// ========== 搜索区域 ==========
|
||||
.search-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background-color: @bg-secondary;
|
||||
border-bottom: 1rpx solid @border-color;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: @bg-primary;
|
||||
border-radius: 32rpx;
|
||||
border: 1rpx solid @border-color;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: @text-primary;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
|
||||
&::placeholder {
|
||||
color: @text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.search-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
padding: 16rpx 28rpx;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 32rpx;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 统计区域 ==========
|
||||
.stats-section {
|
||||
padding: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 28rpx 24rpx;
|
||||
background-color: @bg-card;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid @border-color;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: @text-primary;
|
||||
|
||||
&.male {
|
||||
color: @male-color;
|
||||
}
|
||||
|
||||
&.female {
|
||||
color: @female-color;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.stats-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background-color: @divider-color;
|
||||
}
|
||||
|
||||
// ========== 客户列表区域 ==========
|
||||
.customer-list {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 20rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.customer-scroll {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-container,
|
||||
.empty-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 40rpx;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: @text-secondary !important;
|
||||
font-size: 26rpx !important;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: @text-secondary !important;
|
||||
font-size: 28rpx !important;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
margin-top: 30rpx;
|
||||
padding: 16rpx 48rpx;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 32rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// ========== 客户卡片 ==========
|
||||
.customer-cards {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.customer-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 28rpx;
|
||||
background-color: @bg-card;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid @border-color;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
// 头像
|
||||
.customer-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
|
||||
&.male {
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
}
|
||||
|
||||
&.female {
|
||||
background: linear-gradient(135deg, #ff6b6b, #ff8787);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
// 客户信息
|
||||
.customer-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.customer-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: @text-primary;
|
||||
}
|
||||
|
||||
.gender-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.male {
|
||||
background-color: rgba(76, 110, 245, 0.1);
|
||||
color: @male-color;
|
||||
}
|
||||
|
||||
&.female {
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
color: @female-color;
|
||||
}
|
||||
}
|
||||
|
||||
.customer-date {
|
||||
font-size: 22rpx;
|
||||
color: @text-muted;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.customer-phone {
|
||||
font-size: 26rpx;
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.customer-location {
|
||||
font-size: 24rpx;
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
.customer-action {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(76, 110, 245, 0.15);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.call {
|
||||
background-color: rgba(76, 110, 245, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 加载更多 ==========
|
||||
.load-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 20rpx;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
font-size: 24rpx;
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
@@ -1,2 +1,116 @@
|
||||
<!--pages/customer/index.wxml-->
|
||||
<text>pages/customer/index.wxml</text>
|
||||
<view class="customer-page">
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-section">
|
||||
<view class="search-bar">
|
||||
<t-icon name="search" size="32rpx" color="#9ca3af" />
|
||||
<input
|
||||
class="search-input"
|
||||
type="text"
|
||||
placeholder="搜索客户姓名或手机号"
|
||||
value="{{searchKey}}"
|
||||
bindinput="onSearchInput"
|
||||
confirm-type="search"
|
||||
bindconfirm="onSearch"
|
||||
/>
|
||||
<view class="search-clear" wx:if="{{searchKey}}" bindtap="onClearSearch">
|
||||
<t-icon name="close-circle" size="32rpx" color="#9ca3af" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-btn" bindtap="onSearch">搜索</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-section" wx:if="{{!isLoading && customerList.length > 0}}">
|
||||
<view class="stats-card">
|
||||
<view class="stats-item">
|
||||
<text class="stats-value">{{stats.total}}</text>
|
||||
<text class="stats-label">客户总数</text>
|
||||
</view>
|
||||
<view class="stats-divider"></view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value male">{{stats.male}}</text>
|
||||
<text class="stats-label">男</text>
|
||||
</view>
|
||||
<view class="stats-divider"></view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-value female">{{stats.female}}</text>
|
||||
<text class="stats-label">女</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客户列表 -->
|
||||
<view class="customer-list">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="customer-scroll"
|
||||
refresher-enabled
|
||||
refresher-triggered="{{isRefreshing}}"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
bindscrolltolower="onReachBottom"
|
||||
>
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-container" wx:if="{{isLoading && customerList.length === 0}}">
|
||||
<t-loading theme="spinner" size="40rpx" text="加载中..." t-class-text="loading-text" />
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" wx:elif="{{!isLoading && customerList.length === 0}}">
|
||||
<t-empty icon="user" description="暂无客户数据" t-class-description="empty-text">
|
||||
<view slot="action">
|
||||
<view class="empty-action" bindtap="refreshData">刷新试试</view>
|
||||
</view>
|
||||
</t-empty>
|
||||
</view>
|
||||
|
||||
<!-- 客户卡片列表 -->
|
||||
<view class="customer-cards" wx:else>
|
||||
<view
|
||||
class="customer-card"
|
||||
wx:for="{{customerList}}"
|
||||
wx:key="_id"
|
||||
data-id="{{item._id}}"
|
||||
bindtap="onCustomerDetail"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<view class="customer-avatar">
|
||||
<image wx:if="{{item.avatar}}" class="avatar-img" src="{{item.avatar}}" mode="aspectFill" />
|
||||
<view wx:else class="avatar-placeholder {{item.sex}}">
|
||||
<text class="avatar-text">{{item.avatarText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客户信息 -->
|
||||
<view class="customer-info">
|
||||
<view class="info-row">
|
||||
<text class="customer-name">{{item.name}}</text>
|
||||
<view class="gender-tag {{item.sex}}" wx:if="{{item.sex}}">
|
||||
<text>{{item.sexLabel}}</text>
|
||||
</view>
|
||||
<text class="customer-date" wx:if="{{item.createdAt}}">{{item.createdAt}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="customer-phone">{{item.mobile}}</text>
|
||||
<text class="customer-location" wx:if="{{item.locationText}}">{{item.locationText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="customer-action">
|
||||
<view class="action-btn call" data-phone="{{item.mobile}}" catchtap="onCallPhone">
|
||||
<t-icon name="call" size="32rpx" color="#4c6ef5" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more">
|
||||
<t-loading wx:if="{{isLoadingMore}}" theme="spinner" size="32rpx" text="加载中..." t-class-text="loading-text" />
|
||||
<text class="no-more" wx:elif="{{!hasMore}}">没有更多了</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,66 +1,90 @@
|
||||
// pages/home/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
todayCount: 0,
|
||||
pendingCount: 0,
|
||||
completedCount: 0,
|
||||
menuList: [
|
||||
{ icon: '/images/icon_order.png', name: '订单管理', url: '/pages/order/index' },
|
||||
{ icon: '/images/icon_patient.png', name: '患者管理', url: '/pages/patient/index' },
|
||||
{ icon: '/images/icon_escort.png', name: '陪诊员管理', url: '/pages/escort/index' },
|
||||
{ icon: '/images/icon_schedule.png', name: '排班管理', url: '/pages/schedule/index' },
|
||||
{ icon: '/images/icon_stats.png', name: '数据统计', url: '/pages/stats/index' },
|
||||
{ icon: '/images/icon_setting.png', name: '系统设置', url: '/pages/setting/index' }
|
||||
],
|
||||
todayOrders: [],
|
||||
statusMap: {
|
||||
pending: '待确认',
|
||||
confirmed: '已确认',
|
||||
in_progress: '进行中',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '暖橙陪诊后台', // 转发标题
|
||||
path: '/pages/home/index',
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline: function () {
|
||||
return {
|
||||
title: '暖橙陪诊后台',
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.getTodayOrders();
|
||||
this.getStats();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.getTodayOrders();
|
||||
this.getStats();
|
||||
},
|
||||
|
||||
async getStats() {
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
const [todayRes, pendingRes, completedRes] = await Promise.all([
|
||||
API.escort.getMyRecords({ appointmentDate: today }),
|
||||
API.escort.getMyRecords({ status: ['pending', 'confirmed'] }),
|
||||
API.escort.getMyRecords({ status: ['completed'] })
|
||||
]);
|
||||
this.setData({
|
||||
todayCount: todayRes.code === 0 ? (todayRes.data.records || []).length : 0,
|
||||
pendingCount: pendingRes.code === 0 ? (pendingRes.data.records || []).length : 0,
|
||||
completedCount: completedRes.code === 0 ? (completedRes.data.records || []).length : 0,
|
||||
});
|
||||
},
|
||||
|
||||
async getTodayOrders() {
|
||||
const res = await API.escort.getMyRecords({
|
||||
appointmentDate: new Date().toISOString().substring(0, 10),
|
||||
});
|
||||
if (res.code == 0) {
|
||||
const records = (res.data.records || []).map(item => {
|
||||
if (item.schedule && item.schedule.date) {
|
||||
const d = new Date(item.schedule.date);
|
||||
item.schedule.date = d.toISOString().substring(0, 10) + ' ' + d.toTimeString().substring(0, 5);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.setData({ todayOrders: records });
|
||||
}
|
||||
},
|
||||
|
||||
navigateTo(e) {
|
||||
const url = e.currentTarget.dataset.url
|
||||
wx.navigateTo({ url })
|
||||
},
|
||||
|
||||
viewAllOrders() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/order/index'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,2 +1,69 @@
|
||||
<!--pages/home/index.wxml-->
|
||||
<text>pages/home/index.wxml</text>
|
||||
<view class="container">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<view class="stats-section">
|
||||
<view class="stats-card">
|
||||
<text class="stats-num">{{todayCount}}</text>
|
||||
<text class="stats-label">今日订单</text>
|
||||
</view>
|
||||
<view class="stats-card">
|
||||
<text class="stats-num">{{pendingCount}}</text>
|
||||
<text class="stats-label">待处理</text>
|
||||
</view>
|
||||
<view class="stats-card">
|
||||
<text class="stats-num">{{completedCount}}</text>
|
||||
<text class="stats-label">已完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<view class="section-title">功能菜单</view>
|
||||
<view class="menu-grid">
|
||||
<view class="menu-item" wx:for="{{menuList}}" wx:key="index" bindtap="navigateTo" data-url="{{item.url}}">
|
||||
<view class="menu-icon">
|
||||
<text class="icon-text">{{item.name[0]}}</text>
|
||||
</view>
|
||||
<text class="menu-name">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日订单 -->
|
||||
<view class="order-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">今日订单</text>
|
||||
<text class="view-all" bindtap="viewAllOrders">查看全部 ></text>
|
||||
</view>
|
||||
<view class="order-list">
|
||||
<view class="order-item" wx:for="{{todayOrders}}" wx:key="_id">
|
||||
<view class="order-header">
|
||||
<text class="order-id">{{item._id}}</text>
|
||||
<text class="order-status status-{{item.status}}">{{statusMap[item.status] || item.status}}</text>
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">患者</text>
|
||||
<text class="info-value">{{item.patient.name}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">医院</text>
|
||||
<text class="info-value">{{item.hospital.name}} · {{item.hospital.department}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">时间</text>
|
||||
<text class="info-value">{{item.schedule.date}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">服务</text>
|
||||
<text class="info-value">{{item.escort.serviceName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">费用</text>
|
||||
<text class="info-value fee-value">¥{{item.payment.totalFee}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1 +1,208 @@
|
||||
/* pages/home/index.wxss */
|
||||
/* pages/home/index.wxss */
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 0;
|
||||
margin: 0 10rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.stats-card:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.stats-card:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.stats-num {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* 功能菜单 */
|
||||
.menu-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
background: #e8f4fd;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 36rpx;
|
||||
color: #3498db;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.menu-name {
|
||||
font-size: 26rpx;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 今日订单 */
|
||||
.order-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.section-header .section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
font-size: 26rpx;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.order-id {
|
||||
font-size: 26rpx;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff3e0;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.status-in_progress {
|
||||
background: #e8f5e9;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.status-confirmed {
|
||||
background: #e3f2fd;
|
||||
color: #2980b9;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff3e0;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background: #f3e5f5;
|
||||
color: #8e44ad;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: #fafafa;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 26rpx;
|
||||
color: #95a5a6;
|
||||
width: 80rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.fee-value {
|
||||
color: #e74c3c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// pages/order/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
// 状态映射配置
|
||||
const STATUS_MAP = {
|
||||
@@ -19,322 +20,6 @@ const STATUS_FILTERS = [
|
||||
{ label: '已取消', value: 'cancelled', count: 0 }
|
||||
];
|
||||
|
||||
// Mock 数据 - 基于 escort_record.js 的数据结构
|
||||
const MOCK_ORDERS = [
|
||||
{
|
||||
_id: 'ORD20240530001',
|
||||
orderNo: '20240530001',
|
||||
userId: 'user001',
|
||||
patient: {
|
||||
name: '张三',
|
||||
mobile: '138****1234',
|
||||
sex: 'male',
|
||||
age: 65,
|
||||
idnumber: '310***********1234'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 1,
|
||||
serviceName: '全程陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '复旦大学附属中山医院',
|
||||
address: '上海市徐汇区枫林路180号',
|
||||
department: '心内科',
|
||||
doctor: '李医生',
|
||||
medicalRecordNo: 'MR202405001'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-06-01T09:00:00.000Z',
|
||||
dateText: '06月01日',
|
||||
startTime: '09:00',
|
||||
endTime: '12:00',
|
||||
duration: 180
|
||||
},
|
||||
attendant: {
|
||||
id: 'att001',
|
||||
name: '王陪诊',
|
||||
sex: 'female'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 298,
|
||||
paidFee: 298,
|
||||
status: 'paid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: '患者行动不便,需要轮椅',
|
||||
escortNote: '',
|
||||
medicalSummary: ''
|
||||
},
|
||||
status: 'pending',
|
||||
statusText: '待确认',
|
||||
meta: {
|
||||
createtime: '2024-05-30T10:00:00.000Z',
|
||||
updatetime: '2024-05-30T10:00:00.000Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'ORD20240529002',
|
||||
orderNo: '20240529002',
|
||||
userId: 'user002',
|
||||
patient: {
|
||||
name: '李四',
|
||||
mobile: '139****5678',
|
||||
sex: 'female',
|
||||
age: 42,
|
||||
idnumber: '310***********5678'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 2,
|
||||
serviceName: '挂号陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '上海交通大学医学院附属瑞金医院',
|
||||
address: '上海市黄浦区瑞金二路197号',
|
||||
department: '内分泌科',
|
||||
doctor: '张医生',
|
||||
medicalRecordNo: 'MR202405002'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-05-31T14:00:00.000Z',
|
||||
dateText: '05月31日',
|
||||
startTime: '14:00',
|
||||
endTime: '16:00',
|
||||
duration: 120
|
||||
},
|
||||
attendant: {
|
||||
id: 'att002',
|
||||
name: '赵陪诊',
|
||||
sex: 'male'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 198,
|
||||
paidFee: 198,
|
||||
status: 'paid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: '需要帮忙取药',
|
||||
escortNote: '',
|
||||
medicalSummary: ''
|
||||
},
|
||||
status: 'confirmed',
|
||||
statusText: '已确认',
|
||||
meta: {
|
||||
createtime: '2024-05-29T08:30:00.000Z',
|
||||
updatetime: '2024-05-29T15:00:00.000Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'ORD20240528003',
|
||||
orderNo: '20240528003',
|
||||
userId: 'user003',
|
||||
patient: {
|
||||
name: '王五',
|
||||
mobile: '137****9012',
|
||||
sex: 'male',
|
||||
age: 78,
|
||||
idnumber: '310***********9012'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 1,
|
||||
serviceName: '全程陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '上海市第六人民医院',
|
||||
address: '上海市徐汇区宜山路600号',
|
||||
department: '骨科',
|
||||
doctor: '刘医生',
|
||||
medicalRecordNo: 'MR202405003'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-05-30T08:30:00.000Z',
|
||||
dateText: '05月30日',
|
||||
startTime: '08:30',
|
||||
endTime: '11:30',
|
||||
duration: 180
|
||||
},
|
||||
attendant: {
|
||||
id: 'att003',
|
||||
name: '陈陪诊',
|
||||
sex: 'female'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 398,
|
||||
paidFee: 200,
|
||||
status: 'partial'
|
||||
},
|
||||
notes: {
|
||||
patientNote: '听力不好,请大声说话',
|
||||
escortNote: '已接到患者,正在前往医院',
|
||||
medicalSummary: ''
|
||||
},
|
||||
status: 'in_progress',
|
||||
statusText: '进行中',
|
||||
meta: {
|
||||
createtime: '2024-05-28T16:00:00.000Z',
|
||||
updatetime: '2024-05-30T08:35:00.000Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'ORD20240525004',
|
||||
orderNo: '20240525004',
|
||||
userId: 'user004',
|
||||
patient: {
|
||||
name: '赵六',
|
||||
mobile: '136****3456',
|
||||
sex: 'female',
|
||||
age: 55,
|
||||
idnumber: '310***********3456'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 3,
|
||||
serviceName: '检查陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '华东医院',
|
||||
address: '上海市静安区延安西路221号',
|
||||
department: '体检中心',
|
||||
doctor: '',
|
||||
medicalRecordNo: 'MR202405004'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-05-28T07:30:00.000Z',
|
||||
dateText: '05月28日',
|
||||
startTime: '07:30',
|
||||
endTime: '11:00',
|
||||
duration: 210
|
||||
},
|
||||
attendant: {
|
||||
id: 'att004',
|
||||
name: '孙陪诊',
|
||||
sex: 'male'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 258,
|
||||
paidFee: 258,
|
||||
status: 'paid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: '需要空腹检查',
|
||||
escortNote: '服务完成,患者已安全送回家',
|
||||
medicalSummary: '完成全身检查,各项指标正常'
|
||||
},
|
||||
status: 'completed',
|
||||
statusText: '已完成',
|
||||
meta: {
|
||||
createtime: '2024-05-25T09:00:00.000Z',
|
||||
updatetime: '2024-05-28T11:30:00.000Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'ORD20240524005',
|
||||
orderNo: '20240524005',
|
||||
userId: 'user005',
|
||||
patient: {
|
||||
name: '钱七',
|
||||
mobile: '135****7890',
|
||||
sex: 'male',
|
||||
age: 33,
|
||||
idnumber: '310***********7890'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 2,
|
||||
serviceName: '挂号陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '上海市第一人民医院',
|
||||
address: '上海市虹口区武进路85号',
|
||||
department: '眼科',
|
||||
doctor: '周医生',
|
||||
medicalRecordNo: 'MR202405005'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-05-27T10:00:00.000Z',
|
||||
dateText: '05月27日',
|
||||
startTime: '10:00',
|
||||
endTime: '11:00',
|
||||
duration: 60
|
||||
},
|
||||
attendant: {
|
||||
id: '',
|
||||
name: '',
|
||||
sex: 'none'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 128,
|
||||
paidFee: 0,
|
||||
status: 'unpaid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: '临时有事,需要取消',
|
||||
escortNote: '',
|
||||
medicalSummary: ''
|
||||
},
|
||||
status: 'cancelled',
|
||||
statusText: '已取消',
|
||||
meta: {
|
||||
createtime: '2024-05-24T14:00:00.000Z',
|
||||
updatetime: '2024-05-26T09:00:00.000Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
_id: 'ORD20240523006',
|
||||
orderNo: '20240523006',
|
||||
userId: 'user006',
|
||||
patient: {
|
||||
name: '孙八',
|
||||
mobile: '134****2468',
|
||||
sex: 'female',
|
||||
age: 60,
|
||||
idnumber: '310***********2468'
|
||||
},
|
||||
escort: {
|
||||
serviceId: 1,
|
||||
serviceName: '全程陪诊服务'
|
||||
},
|
||||
hospital: {
|
||||
province: '上海',
|
||||
name: '上海中医药大学附属龙华医院',
|
||||
address: '上海市徐汇区宛平南路725号',
|
||||
department: '中医内科',
|
||||
doctor: '吴医生',
|
||||
medicalRecordNo: 'MR202405006'
|
||||
},
|
||||
schedule: {
|
||||
date: '2024-06-02T08:00:00.000Z',
|
||||
dateText: '06月02日',
|
||||
startTime: '08:00',
|
||||
endTime: '11:00',
|
||||
duration: 180
|
||||
},
|
||||
attendant: {
|
||||
id: 'att005',
|
||||
name: '周陪诊',
|
||||
sex: 'female'
|
||||
},
|
||||
payment: {
|
||||
totalFee: 328,
|
||||
paidFee: 328,
|
||||
status: 'paid'
|
||||
},
|
||||
notes: {
|
||||
patientNote: ' prefer 女陪诊员',
|
||||
escortNote: '',
|
||||
medicalSummary: ''
|
||||
},
|
||||
status: 'pending',
|
||||
statusText: '待确认',
|
||||
meta: {
|
||||
createtime: '2024-05-23T11:00:00.000Z',
|
||||
updatetime: '2024-05-23T11:00:00.000Z'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
@@ -376,7 +61,6 @@ Page({
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
// 页面显示时刷新数据
|
||||
if (this.data.orderList.length > 0) {
|
||||
this.refreshData();
|
||||
}
|
||||
@@ -468,7 +152,10 @@ Page({
|
||||
return orders.map(order => ({
|
||||
...order,
|
||||
statusText: STATUS_MAP[order.status]?.text || order.status,
|
||||
'schedule.dateText': this.formatDate(order.schedule.date)
|
||||
schedule: {
|
||||
...order.schedule,
|
||||
dateText: this.formatDate(order.schedule?.date)
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -482,40 +169,53 @@ Page({
|
||||
|
||||
this.setData({ isLoading: !isRefresh, isLoadingMore: isRefresh && page > 1 });
|
||||
|
||||
// 模拟 API 请求延迟
|
||||
setTimeout(() => {
|
||||
// 筛选数据
|
||||
let filteredOrders = MOCK_ORDERS;
|
||||
if (currentStatus) {
|
||||
filteredOrders = MOCK_ORDERS.filter(order => order.status === currentStatus);
|
||||
}
|
||||
const params = {
|
||||
page,
|
||||
pageSize,
|
||||
status: 'pending,confirmed,in_progress,completed,cancelled'
|
||||
};
|
||||
|
||||
// 分页
|
||||
const start = (page - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
const pageData = filteredOrders.slice(start, end);
|
||||
API.escort.getMyRecords(params)
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '获取订单失败', icon: 'none' });
|
||||
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
const processedOrders = this.processOrders(pageData);
|
||||
const data = res.data || {};
|
||||
const list = data.records || [];
|
||||
let total = list.length || 0;
|
||||
|
||||
// 计算统计
|
||||
const stats = this.calculateStats(MOCK_ORDERS);
|
||||
this.updateFilterCounts(stats);
|
||||
const processedOrders = this.processOrders(list);
|
||||
|
||||
// 更新列表
|
||||
const orderList = isRefresh && page > 1
|
||||
? [...this.data.orderList, ...processedOrders]
|
||||
: processedOrders;
|
||||
const allOrders = isRefresh && page > 1
|
||||
? [...this.data.orderList, ...processedOrders]
|
||||
: processedOrders;
|
||||
|
||||
this.setData({
|
||||
orderList,
|
||||
stats,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
isRefreshing: false,
|
||||
hasMore: end < filteredOrders.length
|
||||
const stats = this.calculateStats(allOrders);
|
||||
this.updateFilterCounts(stats);
|
||||
|
||||
const { currentStatus } = this.data;
|
||||
const orderList = currentStatus
|
||||
? allOrders.filter(order => order.status === currentStatus)
|
||||
: allOrders;
|
||||
total = orderList.length;
|
||||
|
||||
this.setData({
|
||||
orderList,
|
||||
stats,
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
isRefreshing: false,
|
||||
hasMore: orderList.length < total
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取订单列表失败', err);
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' });
|
||||
this.setData({ isLoading: false, isLoadingMore: false, isRefreshing: false });
|
||||
});
|
||||
}, 600);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -562,9 +262,6 @@ Page({
|
||||
const order = this.data.orderList.find(item => item._id === id);
|
||||
if (!order) return;
|
||||
|
||||
// 阻止冒泡,防止触发卡片点击
|
||||
e.stopPropagation();
|
||||
|
||||
const actionMap = {
|
||||
confirm: { title: '确认订单', content: '确认接受此订单?', nextStatus: 'confirmed' },
|
||||
cancel: { title: '取消订单', content: '确定要取消此订单?', nextStatus: 'cancelled' },
|
||||
@@ -589,7 +286,6 @@ Page({
|
||||
confirmColor: '#4c6ef5',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 更新订单状态
|
||||
this.updateOrderStatus(id, actionConfig.nextStatus);
|
||||
}
|
||||
}
|
||||
@@ -602,34 +298,38 @@ Page({
|
||||
updateOrderStatus(id, newStatus) {
|
||||
wx.showLoading({ title: '处理中...' });
|
||||
|
||||
// 模拟 API 请求
|
||||
setTimeout(() => {
|
||||
const orderList = this.data.orderList.map(order => {
|
||||
if (order._id === id) {
|
||||
return {
|
||||
...order,
|
||||
status: newStatus,
|
||||
statusText: STATUS_MAP[newStatus]?.text || newStatus,
|
||||
'meta.updatetime': new Date().toISOString()
|
||||
};
|
||||
API.escort.updateStatus(id, { status: newStatus })
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
wx.showToast({ title: res.message || '操作失败', icon: 'none' });
|
||||
wx.hideLoading();
|
||||
return;
|
||||
}
|
||||
return order;
|
||||
});
|
||||
|
||||
// 重新计算统计
|
||||
const allOrders = MOCK_ORDERS.map(order => {
|
||||
if (order._id === id) {
|
||||
return { ...order, status: newStatus };
|
||||
}
|
||||
return order;
|
||||
});
|
||||
const stats = this.calculateStats(allOrders);
|
||||
this.updateFilterCounts(stats);
|
||||
const orderList = this.data.orderList.map(order => {
|
||||
if (order._id === id) {
|
||||
return {
|
||||
...order,
|
||||
status: newStatus,
|
||||
statusText: STATUS_MAP[newStatus]?.text || newStatus,
|
||||
'meta.updatetime': new Date().toISOString()
|
||||
};
|
||||
}
|
||||
return order;
|
||||
});
|
||||
|
||||
this.setData({ orderList, stats });
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '操作成功', icon: 'success' });
|
||||
}, 500);
|
||||
const stats = this.calculateStats(orderList);
|
||||
this.updateFilterCounts(stats);
|
||||
|
||||
this.setData({ orderList, stats });
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: '操作成功', icon: 'success' });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('更新订单状态失败', err);
|
||||
wx.showToast({ title: '网络错误,请重试', icon: 'none' });
|
||||
wx.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "订单管理",
|
||||
"navigationBarBackgroundColor": "#1a1f3c",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0f1535",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"t-tabs": "tdesign-miniprogram/tabs/tabs",
|
||||
"t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
/* pages/order/index.less */
|
||||
|
||||
// 颜色变量
|
||||
@bg-primary: #0f1535;
|
||||
@bg-secondary: #1a1f3c;
|
||||
@bg-card: #1e2548;
|
||||
@bg-card-hover: #252d5a;
|
||||
// 颜色变量 - 参考图浅色社交风格
|
||||
@bg-primary: #f5f6fa;
|
||||
@bg-secondary: #ffffff;
|
||||
@bg-card: #ffffff;
|
||||
@accent-primary: #4c6ef5;
|
||||
@accent-secondary: #6b7aff;
|
||||
@accent-gradient-start: #4c6ef5;
|
||||
@accent-gradient-end: #748ffc;
|
||||
@text-primary: #ffffff;
|
||||
@text-secondary: #a0a8d0;
|
||||
@text-muted: #6b7298;
|
||||
@border-color: #2a3366;
|
||||
@text-primary: #1a1a2e;
|
||||
@text-secondary: #6b7280;
|
||||
@text-muted: #9ca3af;
|
||||
@border-color: #e5e7eb;
|
||||
@status-pending: #f59f00;
|
||||
@status-confirmed: #4c6ef5;
|
||||
@status-in-progress: #20c997;
|
||||
@status-completed: #51cf66;
|
||||
@status-cancelled: #ff6b6b;
|
||||
@divider-color: #2a3366;
|
||||
@divider-color: #f3f4f6;
|
||||
|
||||
page {
|
||||
background-color: @bg-primary;
|
||||
@@ -33,79 +32,11 @@ page {
|
||||
background-color: @bg-primary;
|
||||
}
|
||||
|
||||
// ========== 顶部统计区域 ==========
|
||||
.header-section {
|
||||
position: relative;
|
||||
padding: 30rpx 30rpx 40rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, @accent-gradient-start 0%, @accent-gradient-end 100%);
|
||||
border-radius: 0 0 40rpx 40rpx;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -100rpx;
|
||||
right: -100rpx;
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
background: radial-gradient(circle, rgba(76, 110, 245, 0.2) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
background: rgba(30, 37, 72, 0.8);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 24rpx;
|
||||
padding: 30rpx 20rpx;
|
||||
border: 1rpx solid rgba(107, 122, 255, 0.1);
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: @text-primary;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: @text-secondary;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: linear-gradient(to bottom, transparent, @divider-color, transparent);
|
||||
}
|
||||
|
||||
// ========== 筛选区域 ==========
|
||||
.filter-section {
|
||||
padding: 20rpx 0;
|
||||
background-color: @bg-primary;
|
||||
background-color: @bg-secondary;
|
||||
border-bottom: 1rpx solid @border-color;
|
||||
}
|
||||
|
||||
.filter-scroll {
|
||||
@@ -114,28 +45,33 @@ page {
|
||||
|
||||
.filter-list {
|
||||
display: inline-flex;
|
||||
padding: 0 20rpx;
|
||||
gap: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 28rpx;
|
||||
background-color: @bg-card;
|
||||
padding: 16rpx 32rpx;
|
||||
background-color: @bg-primary;
|
||||
border-radius: 32rpx;
|
||||
border: 1rpx solid transparent;
|
||||
border: 1rpx solid @border-color;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.3);
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
|
||||
|
||||
.filter-text {
|
||||
color: @text-primary;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-badge {
|
||||
background-color: #ffffff;
|
||||
color: @accent-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +92,7 @@ page {
|
||||
background-color: @status-cancelled;
|
||||
border-radius: 16rpx;
|
||||
font-size: 20rpx;
|
||||
color: @text-primary;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -168,7 +104,7 @@ page {
|
||||
|
||||
.order-scroll {
|
||||
height: 100%;
|
||||
padding: 0 20rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.loading-container,
|
||||
@@ -193,7 +129,7 @@ page {
|
||||
margin-top: 30rpx;
|
||||
padding: 16rpx 48rpx;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
color: @text-primary;
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 32rpx;
|
||||
display: inline-block;
|
||||
@@ -201,7 +137,7 @@ page {
|
||||
|
||||
// ========== 订单卡片 ==========
|
||||
.order-cards {
|
||||
padding-bottom: 40rpx;
|
||||
padding: 24rpx 0 40rpx;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
@@ -209,34 +145,13 @@ page {
|
||||
background-color: @bg-card;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid @border-color;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: @bg-card-hover;
|
||||
}
|
||||
|
||||
// 状态左边框
|
||||
&.pending {
|
||||
border-left: 4rpx solid @status-pending;
|
||||
}
|
||||
|
||||
&.confirmed {
|
||||
border-left: 4rpx solid @status-confirmed;
|
||||
}
|
||||
|
||||
&.in_progress {
|
||||
border-left: 4rpx solid @status-in-progress;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
border-left: 4rpx solid @status-completed;
|
||||
}
|
||||
|
||||
&.cancelled {
|
||||
border-left: 4rpx solid @status-cancelled;
|
||||
opacity: 0.85;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +159,7 @@ page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 28rpx 16rpx;
|
||||
padding: 28rpx 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.order-id {
|
||||
@@ -256,38 +171,39 @@ page {
|
||||
.order-id-text {
|
||||
font-size: 24rpx;
|
||||
color: @text-muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&.pending {
|
||||
background-color: rgba(245, 159, 0, 0.15);
|
||||
background-color: rgba(245, 159, 0, 0.1);
|
||||
color: @status-pending;
|
||||
}
|
||||
|
||||
&.confirmed {
|
||||
background-color: rgba(76, 110, 245, 0.15);
|
||||
color: @accent-secondary;
|
||||
background-color: rgba(76, 110, 245, 0.1);
|
||||
color: @accent-primary;
|
||||
}
|
||||
|
||||
&.in_progress {
|
||||
background-color: rgba(32, 201, 151, 0.15);
|
||||
background-color: rgba(32, 201, 151, 0.1);
|
||||
color: @status-in-progress;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background-color: rgba(81, 207, 102, 0.15);
|
||||
background-color: rgba(81, 207, 102, 0.1);
|
||||
color: @status-completed;
|
||||
}
|
||||
|
||||
&.cancelled {
|
||||
background-color: rgba(255, 107, 107, 0.15);
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
color: @status-cancelled;
|
||||
}
|
||||
}
|
||||
@@ -298,72 +214,75 @@ page {
|
||||
}
|
||||
|
||||
.patient-row {
|
||||
margin-bottom: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.patient-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.patient-avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 28rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: @text-primary;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.patient-detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.patient-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-size: 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: @text-primary;
|
||||
}
|
||||
|
||||
.patient-gender {
|
||||
font-size: 22rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 6rpx;
|
||||
background-color: rgba(107, 122, 255, 0.15);
|
||||
color: @accent-secondary;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: rgba(76, 110, 245, 0.1);
|
||||
color: @accent-primary;
|
||||
font-weight: 500;
|
||||
|
||||
&.male {
|
||||
background-color: rgba(76, 110, 245, 0.15);
|
||||
background-color: rgba(76, 110, 245, 0.1);
|
||||
color: @accent-primary;
|
||||
}
|
||||
|
||||
&.female {
|
||||
background-color: rgba(255, 107, 107, 0.15);
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
color: @status-cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
.patient-age {
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
color: @text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.patient-phone {
|
||||
@@ -373,25 +292,25 @@ page {
|
||||
|
||||
.info-divider {
|
||||
height: 1rpx;
|
||||
background: linear-gradient(to right, transparent, @divider-color, transparent);
|
||||
margin: 16rpx 0;
|
||||
background-color: @divider-color;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
gap: 14rpx;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.hospital-name {
|
||||
font-size: 26rpx;
|
||||
font-size: 28rpx;
|
||||
color: @text-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -404,16 +323,19 @@ page {
|
||||
.service-name {
|
||||
font-size: 26rpx;
|
||||
color: @text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 26rpx;
|
||||
color: @text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.attendant-name {
|
||||
font-size: 26rpx;
|
||||
color: @text-secondary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// ========== 卡片底部 ==========
|
||||
@@ -421,9 +343,9 @@ page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 28rpx 24rpx;
|
||||
margin-top: 8rpx;
|
||||
border-top: 1rpx solid rgba(42, 51, 102, 0.5);
|
||||
padding: 24rpx 28rpx 28rpx;
|
||||
margin-top: 12rpx;
|
||||
border-top: 1rpx solid @divider-color;
|
||||
}
|
||||
|
||||
.fee-section {
|
||||
@@ -438,7 +360,7 @@ page {
|
||||
}
|
||||
|
||||
.fee-value {
|
||||
font-size: 36rpx;
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
color: @status-pending;
|
||||
}
|
||||
@@ -452,7 +374,7 @@ page {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 14rpx 32rpx;
|
||||
padding: 16rpx 36rpx;
|
||||
border-radius: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
@@ -466,17 +388,17 @@ page {
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, @accent-gradient-start, @accent-gradient-end);
|
||||
color: @text-primary;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.3);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: transparent;
|
||||
background-color: @bg-primary;
|
||||
color: @text-secondary;
|
||||
border: 1rpx solid @border-color;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(107, 122, 255, 0.1);
|
||||
background-color: rgba(76, 110, 245, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,222 @@
|
||||
// pages/mine/index.js
|
||||
const API = require('../../utils/api.js')
|
||||
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
phoneNumber: '',
|
||||
version: '1.0.0',
|
||||
showLoginPopup: false,
|
||||
loginForm: {
|
||||
name: '',
|
||||
mobile: ''
|
||||
},
|
||||
menuList: [
|
||||
{ icon: 'user', title: '个人资料', url: '' },
|
||||
{ icon: 'notification', title: '消息通知', url: '' },
|
||||
{ icon: 'lock-on', title: '账号安全', url: '' },
|
||||
{ icon: 'help-circle', title: '帮助中心', url: '' },
|
||||
{ icon: 'info-circle', title: '关于我们', url: '' },
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
onLoad() {
|
||||
this.checkLoginStatus()
|
||||
this.setData({ version: this.getAppVersion() })
|
||||
|
||||
const app = getApp()
|
||||
app.eventBus.on('user-login', this.onUserLogin)
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
|
||||
const app = getApp()
|
||||
app.eventBus.off('user-login', this.onUserLogin)
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
onShow() {
|
||||
this.checkLoginStatus()
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
onUserLogin(user) {
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
userInfo: user,
|
||||
phoneNumber: this.maskPhoneNumber(user.mobile || '')
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
checkLoginStatus() {
|
||||
const app = getApp()
|
||||
const user = app.globalData.user
|
||||
if (user && user.security && user.security.token) {
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
userInfo: user,
|
||||
phoneNumber: this.maskPhoneNumber(user.mobile || '')
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
maskPhoneNumber(phone) {
|
||||
if (!phone || phone.length < 7) return phone
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
},
|
||||
|
||||
getAppVersion() {
|
||||
const accountInfo = wx.getAccountInfoSync()
|
||||
return accountInfo.miniProgram.version || '1.0.0'
|
||||
},
|
||||
|
||||
onShowLoginPopup() {
|
||||
this.setData({ showLoginPopup: true })
|
||||
},
|
||||
|
||||
onCloseLoginPopup() {
|
||||
this.setData({ showLoginPopup: false, loginForm: { name: '', mobile: '' } })
|
||||
},
|
||||
|
||||
onNameInput(e) {
|
||||
this.setData({ 'loginForm.name': e.detail.value })
|
||||
},
|
||||
|
||||
onMobileInput(e) {
|
||||
this.setData({ 'loginForm.mobile': e.detail.value })
|
||||
},
|
||||
|
||||
onLoginSubmit() {
|
||||
const { name, mobile } = this.data.loginForm
|
||||
|
||||
if (!name.trim()) {
|
||||
wx.showToast({ title: '请输入姓名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!mobile.trim()) {
|
||||
wx.showToast({ title: '请输入手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/
|
||||
if (!phoneReg.test(mobile.trim())) {
|
||||
wx.showToast({ title: '手机号格式不正确', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '登录中...' })
|
||||
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
if (res.code) {
|
||||
API.user.wxSignin({ phoneNumber: mobile.trim(), name: name.trim(), code: res.code })
|
||||
.then((data) => {
|
||||
if (data.code == 0) {
|
||||
const app = getApp()
|
||||
app.globalData.user = data.data.user
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
userInfo: app.globalData.user,
|
||||
phoneNumber: this.maskPhoneNumber(app.globalData.user.profile.mobile || ''),
|
||||
showLoginPopup: false,
|
||||
loginForm: { name: '', mobile: '' }
|
||||
})
|
||||
|
||||
wx.showToast({ title: '登录成功', icon: 'success' })
|
||||
} else {
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
}
|
||||
|
||||
wx.hideLoading()
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: '登录失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('登录失败', err)
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
}).finally(() => {
|
||||
wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
onMenuTap(e) {
|
||||
const { index } = e.currentTarget.dataset
|
||||
const item = this.data.menuList[index]
|
||||
|
||||
if (!this.data.isLoggedIn) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
wx.navigateTo({ url: item.url })
|
||||
} else {
|
||||
wx.showToast({ title: '功能开发中', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
wx.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出登录吗?',
|
||||
confirmColor: '#4c6ef5',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.showLoading({ title: '退出中...' })
|
||||
|
||||
API.user.signout().then(() => {
|
||||
this.doLogout()
|
||||
}).catch(() => {
|
||||
this.doLogout()
|
||||
}).finally(() => {
|
||||
wx.hideLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
doLogout() {
|
||||
const app = getApp()
|
||||
app.globalData.user = null
|
||||
wx.removeStorageSync('admin_user_info')
|
||||
wx.removeStorageSync('admin_ai_session_id')
|
||||
wx.removeStorageSync('admin_ai_chat_history')
|
||||
|
||||
this.setData({
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
phoneNumber: ''
|
||||
})
|
||||
|
||||
wx.showToast({ title: '已退出登录', icon: 'success' })
|
||||
},
|
||||
|
||||
onPopupContentTap() {
|
||||
// 阻止冒泡,防止点击弹窗内容时关闭弹窗
|
||||
},
|
||||
|
||||
onClearCache() {
|
||||
wx.showModal({
|
||||
title: '清除缓存',
|
||||
content: '确定要清除所有缓存数据吗?',
|
||||
confirmColor: '#4c6ef5',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.clearStorage({
|
||||
success: () => {
|
||||
const app = getApp()
|
||||
app.globalData.user = null
|
||||
this.setData({
|
||||
isLoggedIn: false,
|
||||
userInfo: null,
|
||||
phoneNumber: ''
|
||||
})
|
||||
wx.showToast({ title: '缓存已清除', icon: 'success' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
{
|
||||
"usingComponents": {}
|
||||
}
|
||||
"navigationBarTitleText": "设置",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,91 @@
|
||||
<!--pages/mine/index.wxml-->
|
||||
<text>pages/mine/index.wxml</text>
|
||||
<view class="set-page">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info" wx:if="{{isLoggedIn}}">
|
||||
<view class="user-avatar">
|
||||
<text class="avatar-text">{{userInfo.nickname ? userInfo.nickname[0] : '管'}}</text>
|
||||
</view>
|
||||
<view class="user-detail">
|
||||
<text class="user-name">{{userInfo.nickname || '管理员'}}</text>
|
||||
<text class="user-phone">{{phoneNumber}}</text>
|
||||
</view>
|
||||
<view class="logout-btn" bindtap="onLogout">
|
||||
<text class="logout-text">退出</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-info login-area" wx:else>
|
||||
<view class="user-avatar default-avatar">
|
||||
<t-icon name="user" size="40rpx" color="#9ca3af" />
|
||||
</view>
|
||||
<view class="user-detail">
|
||||
<text class="login-title">未登录</text>
|
||||
<text class="login-desc">登录后查看更多信息</text>
|
||||
</view>
|
||||
<view class="login-btn" bindtap="onShowLoginPopup">
|
||||
<text class="login-btn-text">立即登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" wx:for="{{menuList}}" wx:key="index" data-index="{{index}}" bindtap="onMenuTap">
|
||||
<view class="menu-left">
|
||||
<t-icon name="{{item.icon}}" size="32rpx" color="#4c6ef5" />
|
||||
<text class="menu-title">{{item.title}}</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<t-icon name="chevron-right" size="28rpx" color="#9ca3af" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 系统操作 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" bindtap="onClearCache">
|
||||
<view class="menu-left">
|
||||
<t-icon name="delete" size="32rpx" color="#ff6b6b" />
|
||||
<text class="menu-title danger">清除缓存</text>
|
||||
</view>
|
||||
<view class="menu-right">
|
||||
<t-icon name="chevron-right" size="28rpx" color="#9ca3af" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录弹窗 -->
|
||||
<view class="login-popup" wx:if="{{showLoginPopup}}" bindtap="onCloseLoginPopup">
|
||||
<view class="login-popup-content" catchtap="onPopupContentTap">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">用户登录</text>
|
||||
<view class="popup-close" bindtap="onCloseLoginPopup">
|
||||
<t-icon name="close" size="32rpx" color="#9ca3af" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-body">
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名</text>
|
||||
<input class="form-input" type="text" placeholder="请输入您的姓名" value="{{loginForm.name}}" bindinput="onNameInput" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">手机号</text>
|
||||
<input class="form-input" type="number" maxlength="11" placeholder="请输入您的手机号" value="{{loginForm.mobile}}" bindinput="onMobileInput" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-footer">
|
||||
<view class="submit-btn" bindtap="onLoginSubmit">
|
||||
<text class="submit-btn-text">登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<view class="version-info">
|
||||
<text class="version-text">版本 {{version}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1 +1,303 @@
|
||||
/* pages/mine/index.wxss */
|
||||
page {
|
||||
background-color: #f5f6fa;
|
||||
color: #1a1a2e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.set-page {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx 32rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.user-info.login-area {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 110, 245, 0.25);
|
||||
}
|
||||
|
||||
.user-avatar.default-avatar {
|
||||
background: #f3f4f6;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: 26rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.login-desc {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
padding: 12rpx 28rpx;
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
font-size: 24rpx;
|
||||
color: #ff6b6b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin: 0;
|
||||
padding: 16rpx 32rpx;
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.login-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.login-btn-text {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 0;
|
||||
border-bottom: 1rpx solid #f3f4f6;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-item:active {
|
||||
background-color: rgba(76, 110, 245, 0.05);
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.menu-title.danger {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 登录弹窗 */
|
||||
.login-popup {
|
||||
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: 1000;
|
||||
}
|
||||
|
||||
.login-popup-content {
|
||||
width: 80%;
|
||||
max-width: 600rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 32rpx 0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.popup-close:active {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f5f6fa;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1a1a2e;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #4c6ef5;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
padding: 0 32rpx 32rpx;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #4c6ef5, #748ffc);
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 110, 245, 0.25);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.submit-btn-text {
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"projectname": "wxapp_admin",
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
|
||||
@@ -6,6 +6,8 @@ const API = {
|
||||
wxSignin: (data) => request.post('/user/wxsignin', data),
|
||||
signout: (data) => request.post('/user/signout', data),
|
||||
update: (data) => request.post('/user/update', data),
|
||||
userInfo: (data) => request.post('/user/userInfo', data),
|
||||
userList: (data) => request.post('/user/list', data),
|
||||
},
|
||||
|
||||
escort: {
|
||||
@@ -15,6 +17,7 @@ const API = {
|
||||
createRecord: (data) => request.post('/health/escort-record', data),
|
||||
updateRecord: (id, data) => request.put(`/health/escort-record/${id}`, data),
|
||||
updateStatus: (id, data) => request.patch(`/health/escort-record/${id}/status`, data),
|
||||
deleteRecord: (id) => request.delete(`/health/escort-record/${id}`),
|
||||
},
|
||||
|
||||
resource: {
|
||||
|
||||
@@ -12,7 +12,8 @@ class Request {
|
||||
const app = getApp()
|
||||
const token = app?.globalData?.user?.security?.token || ''
|
||||
|
||||
data.appId = 'wxapp-escort'
|
||||
data.appId = 'wxapp-escort-admin'
|
||||
data.token = token
|
||||
|
||||
wx.request({
|
||||
url: url.startsWith('http') ? url : `${this.baseURL}${url}`,
|
||||
@@ -45,6 +46,18 @@ class Request {
|
||||
post(url, data = {}, options = {}) {
|
||||
return this.request({ url, method: 'POST', data, ...options })
|
||||
}
|
||||
|
||||
put(url, data = {}, options = {}) {
|
||||
return this.request({ url, method: 'PUT', data, ...options })
|
||||
}
|
||||
|
||||
patch(url, data = {}, options = {}) {
|
||||
return this.request({ url, method: 'PATCH', data, ...options })
|
||||
}
|
||||
|
||||
delete(url, data = {}, options = {}) {
|
||||
return this.request({ url, method: 'DELETE', data, ...options })
|
||||
}
|
||||
}
|
||||
|
||||
const request = new Request()
|
||||
|
||||
Reference in New Issue
Block a user