macmini 2 тижнів тому
батько
коміт
a7aed8de66
5 змінених файлів з 300 додано та 152 видалено
  1. 11 9
      src/main.ts
  2. 136 43
      src/pages/income/income.vue
  3. BIN
      src/static/images/income/income-bg.png
  4. 49 0
      src/utils/directive.ts
  5. 104 100
      src/utils/index.ts

+ 11 - 9
src/main.ts

@@ -2,20 +2,22 @@ import uviewPlus from 'uview-plus'
 import { createSSRApp } from 'vue'
 import App from './App.vue'
 import { requestInterceptor } from './http/interceptor'
-
 import { routeInterceptor } from './router/interceptor'
 import store from './store'
+import { registerGlobalFilters } from './utils/directive'
 import '@/style/index.scss'
 import 'virtual:uno.css'
 
 export function createApp() {
-  const app = createSSRApp(App)
-  app.use(store)
-  app.use(uviewPlus)
-  app.use(routeInterceptor)
-  app.use(requestInterceptor)
+    const app = createSSRApp(App)
+    app.use(store)
+    app.use(uviewPlus)
+    app.use(routeInterceptor)
+    app.use(requestInterceptor)
+    // 注册全局过滤器
+    registerGlobalFilters(app)
 
-  return {
-    app,
-  }
+    return {
+        app,
+    }
 }

+ 136 - 43
src/pages/income/income.vue

@@ -4,6 +4,7 @@ import { ref } from 'vue'
 import { LOGIN_PAGE } from '@/router/config'
 import { useUserStore } from '@/store'
 import { useTokenStore } from '@/store/token'
+import { changtime } from '@/utils'
 
 definePage({
     style: {
@@ -78,12 +79,33 @@ function menuClick(page: string) {
         url: `/pages-A/${page}/index`,
     })
 }
+
+const show = ref(false)
+const filterValue = ref(Number(new Date()))
+function confirm() {
+    // 函数实现
+    show.value = false
+}
+function cancel() {
+    show.value = false
+}
+function close() {
+    show.value = false
+}
+function showTimeFilter() {
+    show.value = true
+}
+
+const activeTab = ref('pending')
+function changeTab(tab: string) {
+    activeTab.value = tab
+}
 </script>
 
 <template>
     <view class="profile-container">
         <!-- 顶部区域 -->
-        <view class="income-header" style="background: url('../../static/images/me/me-bg.png') no-repeat center center; background-size: cover;">
+        <view class="income-header" style="background: url('../../static/images/income/income-bg.png') no-repeat center center; background-size: cover;">
             <view class="income-header-avatar-info" :style="{ paddingTop: `${navigationBarHeight}px` }">
                 <view class="income-header-balance">
                     当前账户余额(元)
@@ -124,7 +146,7 @@ function menuClick(page: string) {
         <!-- 公告 -->
         <view class="income-header-notice">
             <view class="income-header-notice-icon">
-                <image src="@/static/images/income/notice.png" mode="" />
+                <image src="@/static/images/income/notice.png" mode="aspectFit" />
             </view>
             <view class="income-header-notice-content">
                 在考核周期内未达标已锁定收益,<text>前往查看~</text>
@@ -134,36 +156,71 @@ function menuClick(page: string) {
         <view class="income-header-menu">
             <!-- 结算筛选 -->
             <view class="income-header-menu-filter">
-                <view class="income-header-menu-filter-item active">
+                <view class="income-header-menu-filter-item" :class="[activeTab === 'pending' ? 'active' : '']" @click="changeTab('pending')">
                     待结算
                 </view>
-                <view class="income-header-menu-filter-item">
+                <view class="income-header-menu-filter-item" :class="[activeTab === 'settled' ? 'active' : '']" @click="changeTab('settled')">
                     已结算
                 </view>
             </view>
-            <view class="income-header-menu-item" @click="menuClick('applyForm')">
-                <view class="income-header-menu-icon">
-                    <image src="@/static/images/me/coupon-need.png" mode="" />
-                    <view class="income-header-menu-text">
-                        发券人申请
-                    </view>
-                </view>
-                <view class="income-header-menu-left">
-                    <u-icon name="arrow-right" color="#979797" size="12" />
+            <!-- 时间筛选 -->
+            <view class="income-menu-time-filter">
+                <view class="income-menu-time-filter-text" @click="showTimeFilter">
+                    <text>{{ changtime(filterValue) }}</text>
+                    <up-icon name="arrow-down" color="#666666" size="14" />
                 </view>
+                <view>合计:¥4396.00</view>
             </view>
-            <view class="income-header-menu-item" @click="handleLogout">
-                <view class="income-header-menu-icon">
-                    <image src="@/static/images/me/loginOut.png" mode="" />
-                    <view class="income-header-menu-text">
-                        退出登录
+            <!-- 优惠券列表 -->
+            <view class="income-header-menu-list">
+                <view class="income-header-menu-item" @click="menuClick('applyForm')">
+                    <view class="income-header-menu-icon">
+                        <image src="@/static/images/me/coupon-need.png" mode="aspectFit" />
+                        <view class="income-header-menu-text">
+                            <view class="income-header-menu-text-nickname">
+                                昵称
+                            </view>
+                            <view>优惠券名称</view>
+                        </view>
+                    </view>
+                    <view class="income-header-menu-left">
+                        <view class="income-header-menu-left-amount">
+                            ¥628.00
+                        </view>
+                        <view class="income-header-menu-left-time">
+                            2023/08/01 10:00:00
+                        </view>
                     </view>
                 </view>
-                <view class="income-header-menu-left">
-                    <u-icon name="arrow-right" color="#979797" size="12" />
+                <view class="income-header-menu-item" @click="menuClick('applyForm')">
+                    <view class="income-header-menu-icon">
+                        <image src="@/static/images/me/coupon-need.png" mode="aspectFit" />
+                        <view class="income-header-menu-text">
+                            <view class="income-header-menu-text-nickname">
+                                昵称
+                            </view>
+                            <view>优惠券名称</view>
+                        </view>
+                    </view>
+                    <view class="income-header-menu-left">
+                        <view class="income-header-menu-left-amount">
+                            ¥628.00
+                        </view>
+                        <view class="income-header-menu-left-time">
+                            2023/08/01 10:00:00
+                        </view>
+                    </view>
                 </view>
             </view>
         </view>
+        <up-datetime-picker
+            v-model="filterValue"
+            :show="show"
+            mode="year-month"
+            close-on-click-overlay
+            @confirm="confirm"
+            @cancel="cancel"
+        />
     </view>
 </template>
 
@@ -172,12 +229,13 @@ function menuClick(page: string) {
   font-family: Alibaba PuHuiTi;
   min-height: 100vh;
   background-color: #f5f5f5;
+  line-height: 1;
   .income-header {
-    height: 575rpx;
+    height: 525rpx;
     .income-header-avatar-info {
       display: flex;
       flex-direction: column;
-      gap: 39rpx;
+      gap: 30rpx;
       padding: 0 24rpx;
       .income-header-balance {
         font-weight: 400;
@@ -202,7 +260,7 @@ function menuClick(page: string) {
             font-weight: 400;
             font-size: 26rpx;
             &.js {
-              background: #ed6b66;
+              background: #da4c47;
               color: #ffffff;
             }
             &.tx {
@@ -216,10 +274,9 @@ function menuClick(page: string) {
     .income-header-tips {
       height: 124rpx;
       display: flex;
-      margin-top: 49rpx;
       background: linear-gradient(114deg, #f67873, #fb847f, #f67873);
       border-radius: 10rpx;
-      margin: 49rpx 24rpx 0 24rpx;
+      margin: 47rpx 24rpx 0 24rpx;
       .income-header-tips-item {
         flex: 1;
         display: flex;
@@ -281,7 +338,7 @@ function menuClick(page: string) {
   }
 
   .income-header-menu {
-    background: #ffffff;
+    // background: #ffffff;
     border-radius: 10rpx 10rpx 0rpx 0rpx;
     margin: 24rpx 24rpx 0;
     .income-header-menu-filter {
@@ -318,33 +375,69 @@ function menuClick(page: string) {
         }
       }
     }
-    .income-header-menu-item {
-      height: 88rpx;
+    .income-menu-time-filter {
       display: flex;
       justify-content: space-between;
       align-items: center;
-      border-bottom: 1px solid #eeeeee;
-      padding: 0 20rpx;
-      &:last-child {
-        border-bottom: none;
+      font-weight: 400;
+      font-size: 24rpx;
+      color: #333333;
+      padding: 50rpx 20rpx 14rpx;
+      background: #ffffff;
+      margin-top: 4rpx;
+      .income-menu-time-filter-text {
+        font-weight: 400;
+        font-size: 26rpx;
+        color: #000000;
+        display: flex;
+        align-items: center;
+        gap: 12rpx;
       }
-      .income-header-menu-icon {
+    }
+    .income-header-menu-list {
+      background: #ffffff;
+      .income-header-menu-item {
+        height: 88rpx;
         display: flex;
-        justify-content: center;
+        justify-content: space-between;
         align-items: center;
-        gap: 13rpx;
-        image {
-          width: 40rpx;
-          height: 40rpx;
+        border-bottom: 1px solid #eeeeee;
+        padding: 32rpx 0 29rpx;
+        margin: 0 20rpx;
+        &:last-child {
+          border-bottom: none;
+        }
+        .income-header-menu-icon {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          gap: 19rpx;
+          image {
+            width: 60rpx;
+            height: 60rpx;
+          }
+          .income-header-menu-text {
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #888888;
+            .income-header-menu-text-nickname {
+              font-size: 28rpx;
+              color: #222222;
+              margin-bottom: 18rpx;
+            }
+          }
         }
-        .income-header-menu-text {
+        .income-header-menu-left {
           font-weight: 400;
-          font-size: 26rpx;
-          color: #333333;
+          font-size: 24rpx;
+          color: #888888;
+          .income-header-menu-left-amount {
+            font-size: 28rpx;
+            color: #222222;
+            margin-bottom: 18rpx;
+          }
         }
       }
-      .income-header-menu-left {
-      }
     }
   }
 }

BIN
src/static/images/income/income-bg.png


+ 49 - 0
src/utils/directive.ts

@@ -0,0 +1,49 @@
+/**
+ * 时间格式化工具函数
+ * @param value 时间戳或日期字符串
+ * @param format 格式化字符串,默认:YYYY-MM-DD HH:mm:ss
+ * @returns 格式化后的时间字符串
+ */
+export function changtime(value: number | string | Date, format: string = 'YYYY年MM月'): string {
+    if (!value)
+        return ''
+
+    const date = new Date(value)
+
+    // 检查日期是否有效
+    if (Number.isNaN(date.getTime()))
+        return ''
+
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    //   const day = String(date.getDate()).padStart(2, '0')
+    //   const hours = String(date.getHours()).padStart(2, '0')
+    //   const minutes = String(date.getMinutes()).padStart(2, '0')
+    //   const seconds = String(date.getSeconds()).padStart(2, '0')
+
+    return format
+        .replace('YYYY', String(year))
+        .replace('MM', month)
+    // .replace('DD', day)
+    // .replace('HH', hours)
+    // .replace('mm', minutes)
+    // .replace('ss', seconds)
+}
+
+/**
+ * 注册全局过滤器(Vue 3 兼容方式)
+ * 在 main.ts 中调用此函数注册全局过滤器
+ */
+export function registerGlobalFilters(app: any): void {
+    // 注册全局属性,用于在模板中通过 $filters.changtime 使用
+    app.config.globalProperties.$filters = {
+        changtime
+    }
+
+    // 注册全局方法,使过滤器语法 | changtime 可以工作
+    app.mixin({
+        methods: {
+            changtime
+        }
+    })
+}

+ 104 - 100
src/utils/index.ts

@@ -1,15 +1,16 @@
 import type { PageMetaDatum, SubPackages } from '@uni-helper/vite-plugin-uni-pages'
 import { isMpWeixin } from '@uni-helper/uni-env'
 import { pages, subPackages } from '@/pages.json'
+import { changtime } from './directive'
 
 export type PageInstance = Page.PageInstance<AnyObject, object> & { $page: Page.PageInstance<AnyObject, object> & { fullPath: string } }
 
 export function getLastPage() {
-  // getCurrentPages() 至少有1个元素,所以不再额外判断
-  // const lastPage = getCurrentPages().at(-1)
-  // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】
-  const pages = getCurrentPages()
-  return pages[pages.length - 1] as PageInstance
+    // getCurrentPages() 至少有1个元素,所以不再额外判断
+    // const lastPage = getCurrentPages().at(-1)
+    // 上面那个在低版本安卓中打包会报错,所以改用下面这个【虽然我加了 src/interceptions/prototype.ts,但依然报错】
+    const pages = getCurrentPages()
+    return pages[pages.length - 1] as PageInstance
 }
 
 /**
@@ -18,31 +19,31 @@ export function getLastPage() {
  * redirectPath 如 '/pages/demo/base/route-interceptor'
  */
 export function currRoute() {
-  const lastPage = getLastPage() as PageInstance
-  if (!lastPage) {
-    return {
-      path: '',
-      query: {},
+    const lastPage = getLastPage() as PageInstance
+    if (!lastPage) {
+        return {
+            path: '',
+            query: {},
+        }
     }
-  }
-  const currRoute = lastPage.$page
-  // console.log('lastPage.$page:', currRoute)
-  // console.log('lastPage.$page.fullpath:', currRoute.fullPath)
-  // console.log('lastPage.$page.options:', currRoute.options)
-  // console.log('lastPage.options:', (lastPage as any).options)
-  // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱
-  const { fullPath } = currRoute
-  // console.log(fullPath)
-  // eg: /pages/login/login?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序)
-  // eg: /pages/login/login?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
-  return parseUrlToObj(fullPath)
+    const currRoute = lastPage.$page
+    // console.log('lastPage.$page:', currRoute)
+    // console.log('lastPage.$page.fullpath:', currRoute.fullPath)
+    // console.log('lastPage.$page.options:', currRoute.options)
+    // console.log('lastPage.options:', (lastPage as any).options)
+    // 经过多端测试,只有 fullPath 靠谱,其他都不靠谱
+    const { fullPath } = currRoute
+    // console.log(fullPath)
+    // eg: /pages/login/login?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor (小程序)
+    // eg: /pages/login/login?redirect=%2Fpages%2Froute-interceptor%2Findex%3Fname%3Dfeige%26age%3D30(h5)
+    return parseUrlToObj(fullPath)
 }
 
 export function ensureDecodeURIComponent(url: string) {
-  if (url.startsWith('%')) {
-    return ensureDecodeURIComponent(decodeURIComponent(url))
-  }
-  return url
+    if (url.startsWith('%')) {
+        return ensureDecodeURIComponent(decodeURIComponent(url))
+    }
+    return url
 }
 /**
  * 解析 url 得到 path 和 query
@@ -50,22 +51,22 @@ export function ensureDecodeURIComponent(url: string) {
  * 输出: {path: /pages/login/login, query: {redirect: /pages/demo/base/route-interceptor}}
  */
 export function parseUrlToObj(url: string) {
-  const [path, queryStr] = url.split('?')
-  // console.log(path, queryStr)
+    const [path, queryStr] = url.split('?')
+    // console.log(path, queryStr)
 
-  if (!queryStr) {
-    return {
-      path,
-      query: {},
+    if (!queryStr) {
+        return {
+            path,
+            query: {},
+        }
     }
-  }
-  const query: Record<string, string> = {}
-  queryStr.split('&').forEach((item) => {
-    const [key, value] = item.split('=')
-    // console.log(key, value)
-    query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y
-  })
-  return { path, query }
+    const query: Record<string, string> = {}
+    queryStr.split('&').forEach((item) => {
+        const [key, value] = item.split('=')
+        // console.log(key, value)
+        query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y
+    })
+    return { path, query }
 }
 /**
  * 得到所有的需要登录的 pages,包括主包和分包的
@@ -73,78 +74,78 @@ export function parseUrlToObj(url: string) {
  * 如果没有传 key,则表示所有的 pages,如果传递了 key, 则表示通过 key 过滤
  */
 export function getAllPages(key?: string) {
-  // 这里处理主包
-  const mainPages = (pages as PageMetaDatum[])
-    .filter(page => !key || page[key])
-    .map(page => ({
-      ...page,
-      path: `/${page.path}`,
-    }))
-
-  // 这里处理分包
-  const subPages: PageMetaDatum[] = []
-  ;(subPackages as SubPackages).forEach((subPageObj) => {
-    // console.log(subPageObj)
-    const { root } = subPageObj
-
-    subPageObj.pages
-      .filter(page => !key || page[key])
-      .forEach((page) => {
-        subPages.push({
-          ...page,
-          path: `/${root}/${page.path}`,
+    // 这里处理主包
+    const mainPages = (pages as PageMetaDatum[])
+        .filter(page => !key || page[key])
+        .map(page => ({
+            ...page,
+            path: `/${page.path}`,
+        }))
+
+    // 这里处理分包
+    const subPages: PageMetaDatum[] = []
+        ; (subPackages as SubPackages).forEach((subPageObj) => {
+            // console.log(subPageObj)
+            const { root } = subPageObj
+
+            subPageObj.pages
+                .filter(page => !key || page[key])
+                .forEach((page) => {
+                    subPages.push({
+                        ...page,
+                        path: `/${root}/${page.path}`,
+                    })
+                })
         })
-      })
-  })
-  const result = [...mainPages, ...subPages]
-  // console.log(`getAllPages by ${key} result: `, result)
-  return result
+    const result = [...mainPages, ...subPages]
+    // console.log(`getAllPages by ${key} result: `, result)
+    return result
 }
 
 export function getCurrentPageI18nKey() {
-  const routeObj = currRoute()
-  const currPage = (pages as PageMetaDatum[]).find(page => `/${page.path}` === routeObj.path)
-  if (!currPage) {
-    console.warn('路由不正确')
-    return ''
-  }
-  console.log(currPage)
-  console.log(currPage.style.navigationBarTitleText)
-  return currPage.style?.navigationBarTitleText || ''
+    const routeObj = currRoute()
+    const currPage = (pages as PageMetaDatum[]).find(page => `/${page.path}` === routeObj.path)
+    if (!currPage) {
+        console.warn('路由不正确')
+        return ''
+    }
+    console.log(currPage)
+    console.log(currPage.style.navigationBarTitleText)
+    return currPage.style?.navigationBarTitleText || ''
 }
 
 /**
  * 根据微信小程序当前环境,判断应该获取的 baseUrl
  */
 export function getEnvBaseUrl() {
-  // 请求基准地址
-  let baseUrl = import.meta.env.VITE_SERVER_BASEURL
-
-  // # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
-  const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run'
-  const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run'
-  const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run'
-
-  // 微信小程序端环境区分
-  if (isMpWeixin) {
-    const {
-      miniProgram: { envVersion },
-    } = uni.getAccountInfoSync()
-
-    switch (envVersion) {
-      case 'develop':
-        baseUrl = VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl
-        break
-      case 'trial':
-        baseUrl = VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl
-        break
-      case 'release':
-        baseUrl = VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl
-        break
+    // 请求基准地址
+    let baseUrl = import.meta.env.VITE_SERVER_BASEURL
+
+    // # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
+    const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://ukw0y1.laf.run'
+    const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://ukw0y1.laf.run'
+    const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://ukw0y1.laf.run'
+
+    // 微信小程序端环境区分
+    if (isMpWeixin) {
+        const {
+            miniProgram: { envVersion },
+        } = uni.getAccountInfoSync()
+
+        switch (envVersion) {
+            case 'develop':
+                baseUrl = VITE_SERVER_BASEURL__WEIXIN_DEVELOP || baseUrl
+                break
+            case 'trial':
+                baseUrl = VITE_SERVER_BASEURL__WEIXIN_TRIAL || baseUrl
+                break
+            case 'release':
+                baseUrl = VITE_SERVER_BASEURL__WEIXIN_RELEASE || baseUrl
+                break
+        }
     }
-  }
 
-  return baseUrl
+    return baseUrl
 }
 
 /**
@@ -157,3 +158,6 @@ export const isDoubleTokenMode = import.meta.env.VITE_AUTH_MODE === 'double'
  * 通常为 /pages/index/index
  */
 export const HOME_PAGE = `/${(pages as PageMetaDatum[]).find(page => page.type === 'home')?.path || (pages as PageMetaDatum[])[0].path}`
+
+// 导出时间格式化函数
+export { changtime }