| @@ -7,7 +7,8 @@ export default defineAppConfig({ | |||||
| 'pages/invite/index', | 'pages/invite/index', | ||||
| 'pages/settings/index', | 'pages/settings/index', | ||||
| 'pages/login/index', | 'pages/login/index', | ||||
| 'pages/web-view/index' | |||||
| 'pages/web-view/index', | |||||
| 'pages/contact-pass-records/index' | |||||
| ], | ], | ||||
| window: { | window: { | ||||
| backgroundTextStyle: 'light', | backgroundTextStyle: 'light', | ||||
| @@ -77,6 +77,14 @@ const onBlockConfirm = (item) => { | |||||
| console.log("error", error) | console.log("error", error) | ||||
| }) | }) | ||||
| } | } | ||||
| const onClickPassRecords = () => { | |||||
| Router.toContactPassRecords({ data: contactData }) | |||||
| } | |||||
| const onClickEditRecords = () => { | |||||
| Router.toContactEdit({ data: contactData }) | |||||
| } | |||||
| const onDeleteConfirm = (item) => { | const onDeleteConfirm = (item) => { | ||||
| console.log(item) | console.log(item) | ||||
| Taro.showLoading({ | Taro.showLoading({ | ||||
| @@ -110,7 +118,7 @@ const onDeleteConfirm = (item) => { | |||||
| <view class="p-3 d-flex"> | <view class="p-3 d-flex"> | ||||
| <view class="flex-grow-1"> | <view class="flex-grow-1"> | ||||
| <view class="h3"> | <view class="h3"> | ||||
| <Text className="fas fa-chevron-left text-primary" @tap="goBack()"/> | |||||
| <view class="fas fa-chevron-left text-primary" @tap="goBack()" hover-class="btn-hover-primary"/> | |||||
| 更新联系人 | 更新联系人 | ||||
| </view> | </view> | ||||
| <view class="text-black-50">更新来访联系人信息,并设置下次到访时间</view> | <view class="text-black-50">更新来访联系人信息,并设置下次到访时间</view> | ||||
| @@ -124,13 +132,13 @@ const onDeleteConfirm = (item) => { | |||||
| <view class="scroll"> | <view class="scroll"> | ||||
| <ContactForm :data="contactData"/> | <ContactForm :data="contactData"/> | ||||
| <nut-cell title="来访记录"> | |||||
| <nut-cell title="来访记录" @tap="onClickPassRecords"> | |||||
| <template #desc> | <template #desc> | ||||
| <Text className="fas fa-chevron-right"/> | <Text className="fas fa-chevron-right"/> | ||||
| </template> | </template> | ||||
| </nut-cell> | </nut-cell> | ||||
| <nut-cell title="编辑记录" sub-title="本联系人在公司中的所有信息修改记录"> | |||||
| <nut-cell title="编辑记录" sub-title="本联系人在公司中的所有信息修改记录" @tap="onClickEditRecords"> | |||||
| <template #desc> | <template #desc> | ||||
| <Text className="fas fa-chevron-right"/> | <Text className="fas fa-chevron-right"/> | ||||
| </template> | </template> | ||||
| @@ -47,7 +47,7 @@ const onSaveClicked = () => { | |||||
| <view class="p-3 d-flex"> | <view class="p-3 d-flex"> | ||||
| <view class="flex-grow-1"> | <view class="flex-grow-1"> | ||||
| <view class="h3"> | <view class="h3"> | ||||
| <Text className="fas fa-chevron-left text-primary" @tap="goBack()"/> | |||||
| <view class="fas fa-chevron-left text-primary" @tap="goBack()" hover-class="btn-hover-primary"/> | |||||
| 新来访人 | 新来访人 | ||||
| </view> | </view> | ||||
| <view class="text-black-50">新建来访联系人,并设置到访时间</view> | <view class="text-black-50">新建来访联系人,并设置到访时间</view> | ||||
| @@ -0,0 +1,6 @@ | |||||
| export default definePageConfig({ | |||||
| navigationBarTitleText: '', | |||||
| usingComponents: {}, | |||||
| navigationStyle: 'custom', | |||||
| disableScroll: true | |||||
| }) | |||||
| @@ -0,0 +1,20 @@ | |||||
| page { | |||||
| height: 100%; | |||||
| } | |||||
| .nut-navbar { | |||||
| background: transparent; | |||||
| } | |||||
| .scroll { | |||||
| flex: 1; | |||||
| overflow: scroll; | |||||
| } | |||||
| .save-button { | |||||
| font-size: 36px | |||||
| } | |||||
| .nut-action-sheet__item { | |||||
| color: red !important; | |||||
| } | |||||
| @@ -0,0 +1,180 @@ | |||||
| <script setup lang="ts"> | |||||
| import { reactive } from 'vue' | |||||
| import { View } from '@tarojs/components' | |||||
| import './index.scss' | |||||
| import {weappAuth} from "../../utils"; | |||||
| import { | |||||
| GroupedContactPassRecords, | |||||
| useContactPassRecordsStore | |||||
| } from "../../stores/contact-pass-records"; | |||||
| import {Router} from "tarojs-router-next"; | |||||
| import { BjxHelper } from "../../utils"; | |||||
| const contactData = Router.getData() | |||||
| console.log("contactData", contactData) | |||||
| const state = reactive<{ | |||||
| isPageDataLoading: boolean, | |||||
| isAuthError: boolean, | |||||
| groupedContactPassRecords: GroupedContactPassRecords[], | |||||
| }>({ | |||||
| isPageDataLoading: true, | |||||
| isAuthError: false, | |||||
| groupedContactPassRecords: [], | |||||
| }) | |||||
| const passRecords = useContactPassRecordsStore() | |||||
| init() | |||||
| function init() { | |||||
| state.isAuthError = false | |||||
| state.isPageDataLoading = true | |||||
| console.log("contactData", contactData) | |||||
| weappAuth().then(r => { | |||||
| console.log(r) | |||||
| passRecords.loadContactPassRecordsFromServer(contactData.id).then( groupedPassRecords => { | |||||
| state.isPageDataLoading = false | |||||
| state.groupedContactPassRecords = groupedPassRecords | |||||
| // console.log(state.groupedPassRecords) | |||||
| }) | |||||
| .catch(error => { | |||||
| console.log("error", error) | |||||
| state.isAuthError = true | |||||
| state.isPageDataLoading = false | |||||
| }) | |||||
| }).catch(e => { | |||||
| console.log("error", e) | |||||
| state.isAuthError = true | |||||
| state.isPageDataLoading = false | |||||
| }) | |||||
| } | |||||
| const onAuthErrorRefresh = () => { | |||||
| init() | |||||
| } | |||||
| // const onPullDownRefresh = () => { | |||||
| // console.log("onPullDownRefresh") | |||||
| // state.isRefresherTriggered = false | |||||
| // } | |||||
| function onPullDownRefresh (e) { | |||||
| console.log("onPullDownRefresh", e) | |||||
| // state.isRefresherTriggered = false | |||||
| } | |||||
| const goBack = () => { | |||||
| Router.back() | |||||
| } | |||||
| </script> | |||||
| <template> | |||||
| <view class="h-100 d-flex flex-column"> | |||||
| <BackgroundBasic/> | |||||
| <NutNavbar title=""></NutNavbar> | |||||
| <view class="p-3 d-flex"> | |||||
| <view class="flex-grow-1"> | |||||
| <view class="h3"> | |||||
| <view class="fas fa-chevron-left text-primary" @tap="goBack()" hover-class="btn-hover-primary"/> | |||||
| 到访记录 | |||||
| </view> | |||||
| <view class="text-black-50">此联系人的最近来访记录</view> | |||||
| </view> | |||||
| <view class="d-flex align-items-end"> | |||||
| <View></View> | |||||
| </view> | |||||
| </view> | |||||
| <View v-if="state.isAuthError"> | |||||
| <nut-empty description="连接出现问题,请稍后重试"> | |||||
| <div style="margin-top: 10px"> | |||||
| <nut-button type="default" @tap="onAuthErrorRefresh">刷新</nut-button> | |||||
| </div> | |||||
| </nut-empty> | |||||
| </View> | |||||
| <View class="scroll 100vh" v-else> | |||||
| <view class="scroll 100vh" v-if="!state.isPageDataLoading"> | |||||
| <Scroll-View style="height: 100vh" :scrollY="true"> | |||||
| <View v-for="dateGroup in state.groupedContactPassRecords"> | |||||
| <View class="h4 pt-3 pl-3">{{dateGroup.date}}</View> | |||||
| <view v-for="item in dateGroup.passRecords" class="border m-3 p-3 bg-light"> | |||||
| <view class="d-flex flex-row"> | |||||
| <view class="flex-grow-1"> | |||||
| <NutAvatar size="large" color="white" :bg-color="BjxHelper.mbString2RgbHex(contactData.name)" class="overflow-hidden"> | |||||
| <img v-if="item.faceUrl" :src="item.faceUrl" /> | |||||
| <view v-else-if="BjxHelper.getBJXFirstChar(contactData.name)">{{BjxHelper.getBJXFirstChar(contactData.name)}}</view> | |||||
| <view v-else> | |||||
| <Text className='fas fa-user fa-lg'/> | |||||
| </view> | |||||
| </NutAvatar> | |||||
| </view> | |||||
| <view class="d-flex align-items-center text-black-50"> | |||||
| {{item.time}} | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| </View> | |||||
| <view v-if="passRecords.passRecords.length >= 100" class="pl-5 pr-5 pt-5 pb-3 text-black-50"> | |||||
| <nut-divider> 仅显示最近 100 条记录 </nut-divider> | |||||
| </view> | |||||
| <view v-else class="container pt-5 pb-3"> | |||||
| <view class="row"> | |||||
| <view class="col"/> | |||||
| <view class="col text-center text-black-50"> | |||||
| <nut-divider> 没有更多了 </nut-divider> | |||||
| </view> | |||||
| <view class="col"/> | |||||
| </view> | |||||
| </view> | |||||
| </Scroll-View> | |||||
| </view> | |||||
| <View v-else class="skeleton"> | |||||
| <NutSkeleton v-for="_ in 10" class="pl-4 pt-4 pb-4 pr-5" height="20px" width="60vw" animated avatar avatar-size="50px" row="2"/> | |||||
| </View> | |||||
| </View> | |||||
| </view> | |||||
| <!-- <view class="p-4">--> | |||||
| <!-- <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">使用电话号码登录</button>--> | |||||
| <!-- </view>--> | |||||
| <!-- <Counter/>--> | |||||
| <!-- <nut-cell title="展示弹出层" is-link @click="show = true"></nut-cell>--> | |||||
| <!-- <nut-popup :style="{ padding: '30px 50px' }" v-model:visible="show">正文</nut-popup>--> | |||||
| <!-- <View><Text>{{state.msg}}</Text></View>--> | |||||
| <!-- <nut-button type="primary" @click="handleClick(state.msg2)">点我</nut-button>--> | |||||
| <!-- <Add color="red" />--> | |||||
| <!-- <div class="alert alert-primary" role="alert">--> | |||||
| <!-- A simple primary alert—check it out!--> | |||||
| <!-- </div>--> | |||||
| <!-- <h1>Example heading <span class="badge bg-secondary">New</span></h1>--> | |||||
| <!-- <button type="button" class="btn btn-primary">--> | |||||
| <!-- Notifications <span class="badge text-bg-secondary">4</span>--> | |||||
| <!-- </button>--> | |||||
| <!-- <button type="button" class="btn btn-primary position-relative">--> | |||||
| <!-- Inbox--> | |||||
| <!-- <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">--> | |||||
| <!-- 99+--> | |||||
| <!-- <span class="visually-hidden">unread messages</span>--> | |||||
| <!-- </span>--> | |||||
| <!-- </button>--> | |||||
| <!-- </view>--> | |||||
| </template> | |||||
| @@ -0,0 +1,86 @@ | |||||
| // https://pinia.esm.dev/introduction.html | |||||
| import { defineStore } from 'pinia' | |||||
| import {GQLRequest, PinyinHelper, Session} from "../utils"; | |||||
| import {gql} from "graphql-tag"; | |||||
| import {ref, Ref, UnwrapRef} from "vue"; | |||||
| import dayjs from "dayjs"; | |||||
| import {PassRecords} from "./pass-records"; | |||||
| export interface ContactPassRecords { | |||||
| id: number | |||||
| faceUrl: string | |||||
| recognizedTime: string | |||||
| date: string | |||||
| time: string | |||||
| } | |||||
| export interface GroupedContactPassRecords { | |||||
| date: string | |||||
| passRecords: ContactPassRecords[] | |||||
| } | |||||
| export const useContactPassRecordsStore = defineStore('contact-pass-records', () => { | |||||
| const passRecords: Ref<UnwrapRef<ContactPassRecords[]>> = ref([]) | |||||
| const groupedPassRecords: Ref<UnwrapRef<GroupedContactPassRecords[]>> = ref([]) | |||||
| const GQL_QUERY_CONTACT_PASS_RECORDS = gql` | |||||
| query ($userId: ID, $limit: Int ) { | |||||
| visitorPassRecords(userId: $userId, limit: $limit) { | |||||
| id | |||||
| faceUrl | |||||
| recognizedTime | |||||
| } | |||||
| } | |||||
| ` | |||||
| function loadContactPassRecordsFromServer (userId: number) { | |||||
| return new Promise((resolve, reject) => { | |||||
| const items: ContactPassRecords[] = [] | |||||
| const limit = 100 | |||||
| console.log("userId", userId) | |||||
| return GQLRequest.query(GQL_QUERY_CONTACT_PASS_RECORDS, {userId, limit}) | |||||
| .then(result => { | |||||
| if (result.code == 500) { | |||||
| reject("Network Error") | |||||
| } else { | |||||
| console.log(result) | |||||
| for (let item of result.data.visitorPassRecords) { | |||||
| // console.log(item) | |||||
| items.push({ | |||||
| id: item.id, | |||||
| userId: item.userId, | |||||
| faceUrl: item.faceUrl, | |||||
| recognizedTime: item.recognizedTime, | |||||
| date: dayjs(item.recognizedTime, "YYYY-MM-DDTHH:mm:ss").format("YYYY年MM月DD日"), | |||||
| time: dayjs(item.recognizedTime, "YYYY-MM-DDTHH:mm:ss").format("HH:mm:ss"), | |||||
| } as ContactPassRecords) | |||||
| } | |||||
| passRecords.value = items.sort((a, b) => a.recognizedTime.localeCompare(b.recognizedTime)).reverse(); | |||||
| groupedPassRecords.value = generatePassRecordsGroupList(passRecords.value) | |||||
| console.log("groupedPassRecords", groupedPassRecords.value) | |||||
| resolve(groupedPassRecords.value) | |||||
| } | |||||
| }).catch(e => { | |||||
| reject(e) | |||||
| }) | |||||
| }) | |||||
| } | |||||
| const generatePassRecordsGroupList = (passRecords: ContactPassRecords[]) => { | |||||
| let groupedPassRecords = [] | |||||
| for (const item of passRecords) { | |||||
| const date = item.date; | |||||
| if (!groupedPassRecords[date]) { | |||||
| groupedPassRecords[date] = { date: date, passRecords: [] }; | |||||
| } | |||||
| groupedPassRecords[date].passRecords.push(item); | |||||
| } | |||||
| return Object.values(groupedPassRecords) | |||||
| } | |||||
| return { loadContactPassRecordsFromServer, passRecords } | |||||
| }) | |||||