li.shaoyi há 2 anos atrás
pai
commit
b1f683b437
58 ficheiros alterados com 905 adições e 1033 exclusões
  1. 4 4
      src/hooks/menu/index.ts
  2. 7 0
      src/hooks/navigation/index.ts
  3. 9 210
      src/mock/router.ts
  4. 3 3
      src/packages/mobile/views/credit/signin/index.vue
  5. 22 3
      src/packages/pc/App.vue
  6. BIN
      src/packages/pc/assets/images/login-bg.jpg
  7. BIN
      src/packages/pc/assets/images/loginBackground.jpg
  8. BIN
      src/packages/pc/assets/images/logoBackground.jpg
  9. BIN
      src/packages/pc/assets/logo.png
  10. 0 0
      src/packages/pc/assets/logo.svg
  11. 1 1
      src/packages/pc/assets/themes/default/variable.less
  12. 42 21
      src/packages/pc/components/base/table/index.less
  13. 12 11
      src/packages/pc/components/base/table/index.ts
  14. 57 12
      src/packages/pc/components/base/table/index.vue
  15. 7 0
      src/packages/pc/components/base/table/interface.ts
  16. 18 19
      src/packages/pc/components/layouts/header/index.less
  17. 12 15
      src/packages/pc/components/layouts/header/index.vue
  18. 43 41
      src/packages/pc/components/layouts/navbar/index.vue
  19. 41 36
      src/packages/pc/components/layouts/sidebar/index.less
  20. 2 3
      src/packages/pc/components/layouts/sidebar/index.vue
  21. 47 0
      src/packages/pc/components/modules/action-menu/index.less
  22. 52 0
      src/packages/pc/components/modules/action-menu/index.ts
  23. 125 0
      src/packages/pc/components/modules/action-menu/index.vue
  24. 16 0
      src/packages/pc/components/modules/action-menu/interface.ts
  25. 2 2
      src/packages/pc/components/modules/auth-component/index.vue
  26. 16 16
      src/packages/pc/components/modules/auth-operation/index.less
  27. 11 13
      src/packages/pc/components/modules/auth-operation/index.vue
  28. 0 4
      src/packages/pc/main.ts
  29. 1 0
      src/packages/pc/router/dynamicRouter.ts
  30. 9 8
      src/packages/pc/router/historyRouter.ts
  31. 6 4
      src/packages/pc/router/index.ts
  32. 61 0
      src/packages/pc/views/auth/components/layout/index.less
  33. 29 0
      src/packages/pc/views/auth/components/layout/index.vue
  34. 0 0
      src/packages/pc/views/auth/login/index.less
  35. 13 6
      src/packages/pc/views/auth/login/index.vue
  36. 60 0
      src/packages/pc/views/bill/index.vue
  37. 26 35
      src/packages/pc/views/boot/index.vue
  38. 0 8
      src/packages/pc/views/home/main/index.vue
  39. 0 12
      src/packages/pc/views/market/chart/components/detail/index.vue
  40. 0 2
      src/packages/pc/views/market/chart/index.less
  41. 0 47
      src/packages/pc/views/market/chart/index.vue
  42. 0 5
      src/packages/pc/views/market/quote/components/detail/index.less
  43. 0 31
      src/packages/pc/views/market/quote/components/detail/index.vue
  44. 0 52
      src/packages/pc/views/market/quote/components/edit/index.vue
  45. 0 12
      src/packages/pc/views/market/quote/components/futures/index.vue
  46. 0 16
      src/packages/pc/views/market/quote/components/spot/index.vue
  47. 0 1
      src/packages/pc/views/market/quote/index.less
  48. 0 121
      src/packages/pc/views/market/quote/index.vue
  49. 61 0
      src/packages/pc/views/member/index.vue
  50. 0 22
      src/packages/pc/views/order/list/index.less
  51. 0 101
      src/packages/pc/views/order/list/index.vue
  52. 13 29
      src/packages/pc/views/system/menu/index.vue
  53. 14 30
      src/packages/pc/views/system/role/index.vue
  54. 0 49
      src/packages/pc/views/user/components/layout/index.less
  55. 0 28
      src/packages/pc/views/user/components/layout/index.vue
  56. 14 0
      src/services/api/common/index.ts
  57. 1 0
      src/types/model/account.d.ts
  58. 48 0
      src/types/model/common.d.ts

+ 4 - 4
src/hooks/menu/index.ts

@@ -116,7 +116,7 @@ export function useMenu(authCode?: string) {
      * @param reverse 
      * @returns 
      */
-    const getAuthButton = (filtered: string[] = [], reverse = false) => authFilter(AuthType.Button, filtered, reverse)
+    const getAuthButtons = (filtered: string[] = [], reverse = false) => authFilter(AuthType.Button, filtered, reverse)
 
     /**
      * 获取权限组件
@@ -124,7 +124,7 @@ export function useMenu(authCode?: string) {
      * @param reverse 
      * @returns 
      */
-    const getAuthComponent = (filtered: string[] = [], reverse = false) => authFilter(AuthType.Component, filtered, reverse)
+    const getAuthComponents = (filtered: string[] = [], reverse = false) => authFilter(AuthType.Component, filtered, reverse)
 
     return {
         route,
@@ -133,7 +133,7 @@ export function useMenu(authCode?: string) {
         componentMap,
         getMenus,
         getChildrenMenus,
-        getAuthButton,
-        getAuthComponent,
+        getAuthButtons,
+        getAuthComponents,
     }
 }

+ 7 - 0
src/hooks/navigation/index.ts

@@ -53,6 +53,12 @@ export function useNavigation() {
         }
     }
 
+    // 返回上个页面
+    const routerBack = <T extends object>(params?: T) => {
+        setGlobalUrlParams(params ?? {})
+        router.back()
+    }
+
     // 路由跳转
     const routerTo = (to: string) => {
         router.push({ name: to })
@@ -77,6 +83,7 @@ export function useNavigation() {
         getQueryString,
         getQueryStringToNumber,
         backTo,
+        routerBack,
         routerTo,
         beforeRouteLeave,
     }

+ 9 - 210
src/mock/router.ts

@@ -9,223 +9,22 @@ const appmenu = {
             {
                 authType: 1,
                 sort: 1,
-                title: '工作台',
-                code: 'home',
-                url: '/home',
+                title: '好友查询',
+                code: 'member',
+                url: '/member',
                 urlType: 1,
-                component: 'Page',
-                icon: 'Platform',
-                children: [
-                    {
-                        authType: 1,
-                        sort: 1,
-                        title: '监控中心',
-                        code: 'home_main',
-                        url: 'main',
-                        urlType: 1,
-                        component: 'views/home/main/index.vue',
-                    },
-                ]
+                component: 'views/member/index.vue',
+                icon: 'UserFilled',
             },
             {
                 authType: 1,
                 sort: 1,
-                title: '期货市场',
-                code: 'market',
-                url: '/market',
+                title: '收益查询',
+                code: 'bill',
+                url: '/bill',
                 urlType: 1,
-                component: 'Page',
+                component: 'views/bill/index.vue',
                 icon: 'TrendCharts',
-                children: [
-                    {
-                        authType: 1,
-                        sort: 1,
-                        title: '行情列表',
-                        code: 'market_quote',
-                        url: 'quote',
-                        urlType: 1,
-                        component: 'views/market/quote/index.vue',
-                        children: [
-                            {
-                                authType: 2,
-                                sort: 1,
-                                title: '现货明细',
-                                code: 'market_quote_spot',
-                                component: 'views/market/quote/components/spot/index.vue',
-                            },
-                            {
-                                authType: 2,
-                                sort: 2,
-                                title: '期货明细',
-                                code: 'market_quote_futures',
-                                component: 'views/market/quote/components/futures/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '新增',
-                                code: 'market_quote_add',
-                                icon: 'Plus',
-                                buttonType: 'primary',
-                                component: 'views/market/quote/components/edit/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '删除',
-                                code: 'market_quote_delete',
-                                icon: 'Delete',
-                                buttonType: 'danger',
-                            },
-                            {
-                                authType: 3,
-                                title: '交易',
-                                code: 'market_quote_trade',
-                                buttonType: 'primary',
-                                component: 'components/modules/trade/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '详情',
-                                code: 'market_quote_detail',
-                                component: 'views/market/quote/components/detail/index.vue',
-                            }
-                        ],
-                    },
-                    {
-                        authType: 1,
-                        sort: 2,
-                        title: '期货图表',
-                        code: 'market_chart',
-                        url: 'chart',
-                        urlType: 1,
-                        component: 'views/market/chart/index.vue'
-                    },
-                ]
-            },
-            {
-                authType: 1,
-                sort: 1,
-                title: '订单管理',
-                code: 'order',
-                url: '/order',
-                urlType: 1,
-                component: 'Page',
-                icon: 'List',
-                children: [
-                    {
-                        authType: 1,
-                        sort: 1,
-                        title: '订单列表',
-                        code: 'order_list',
-                        url: 'list',
-                        urlType: 1,
-                        component: 'views/order/list/index.vue',
-                        children: [
-                            {
-                                authType: 3,
-                                title: '修改',
-                                code: 'order_list_modify',
-                            },
-                            {
-                                authType: 3,
-                                title: '详情',
-                                code: 'order_list_detail',
-                                children: [
-                                    {
-                                        authType: 3,
-                                        title: '审核',
-                                        code: 'order_list_detail_audit',
-                                    }
-                                ]
-                            }
-                        ],
-                    },
-                ]
-            },
-            {
-                authType: 1,
-                sort: 1,
-                title: '系统管理',
-                code: 'system',
-                url: '/system',
-                urlType: 1,
-                component: 'Page',
-                icon: 'Setting',
-                children: [
-                    {
-                        authType: 1,
-                        sort: 1,
-                        title: '菜单管理',
-                        code: 'system_menu',
-                        url: 'menu',
-                        urlType: 1,
-                        component: 'views/system/menu/index.vue',
-                        children: [
-                            {
-                                authType: 3,
-                                title: '新增',
-                                code: 'system_menu_add',
-                                icon: 'Plus',
-                                buttonType: 'primary',
-                                component: 'views/system/menu/components/edit/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '编辑',
-                                code: 'system_menu_edit',
-                                icon: 'Edit',
-                                buttonType: 'primary',
-                                component: 'views/system/menu/components/edit/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '删除',
-                                code: 'system_menu_delete',
-                                icon: 'Delete',
-                                buttonType: 'danger',
-                            }
-                        ]
-                    },
-                    {
-                        authType: 1,
-                        sort: 1,
-                        title: '角色管理',
-                        code: 'system_role',
-                        url: 'role',
-                        urlType: 1,
-                        component: 'views/system/role/index.vue',
-                        children: [
-                            {
-                                authType: 3,
-                                title: '新增',
-                                code: 'system_role_add',
-                                icon: 'Plus',
-                                buttonType: 'primary',
-                            },
-                            {
-                                authType: 3,
-                                title: '权限',
-                                code: 'system_role_auth',
-                                icon: 'Lock',
-                                buttonType: 'primary',
-                                component: 'views/system/role/components/auth/index.vue',
-                            },
-                            {
-                                authType: 3,
-                                title: '编辑',
-                                code: 'system_role_edit',
-                                icon: 'Edit',
-                                buttonType: 'primary',
-                            },
-                            {
-                                authType: 3,
-                                title: '删除',
-                                code: 'system_role_delete',
-                                icon: 'Delete',
-                                buttonType: 'danger',
-                            }
-                        ]
-                    },
-                ]
             },
         ]
     }

+ 3 - 3
src/packages/mobile/views/credit/signin/index.vue

@@ -116,7 +116,7 @@ import { signin } from '@/services/api/common'
 import { useNavigation } from '@/hooks/navigation'
 
 const { getUserId } = useLoginStore()
-const { routerTo, backTo } = useNavigation()
+const { routerTo, routerBack } = useNavigation()
 const headerRef = shallowRef<HTMLDivElement>()
 const userAccount = shallowRef<Partial<Model.UserAccount>>({})
 const scoreConfig = shallowRef<Model.THJScoreConfigRsp[]>([])
@@ -134,14 +134,14 @@ const getScoreConfig = (value: number) => {
 
 // 跳转到采购
 const toPurchase = () => {
-    backTo('Home', {
+    routerBack({
         tabName: 'purchase'
     })
 }
 
 // 跳转到供求
 const toSupplyDemand = () => {
-    backTo('Home', {
+    routerBack({
         tabName: 'supplyDemand'
     })
 }

+ 22 - 3
src/packages/pc/App.vue

@@ -1,6 +1,7 @@
 <template>
   <el-config-provider :locale="zhCn">
-    <router-view />
+    <app-page v-if="hasLogin" />
+    <router-view v-else />
   </el-config-provider>
 </template>
 
@@ -11,18 +12,36 @@ export default {
 </script>-->
 
 <script lang="ts" setup>
-import { useRouter } from 'vue-router'
+import { ref, watch } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
 import { useAuth } from '@/business/auth'
+import { useLoginStore } from '@/stores'
 import zhCn from 'element-plus/lib/locale/lang/zh-cn'
 import eventBus from '@/services/bus'
 
+const { getToken } = useLoginStore()
 const { logout } = useAuth()
+const route = useRoute()
 const router = useRouter()
+const hasLogin = ref(false)
 
 // 接收用户登出通知
-eventBus.$on('LogoutNotify', () => {
+eventBus.$on('LogoutNotify', (msg) => {
   logout(() => {
+    if (msg) {
+      ElMessageBox.alert(msg as string)
+    }
     router.replace({ name: 'login' })
   })
 })
+
+watch(() => route.name, (routeName) => {
+  const token = getToken()
+  if (routeName === 'boot' || routeName === 'login' || !token) {
+    hasLogin.value = false
+  } else {
+    hasLogin.value = true
+  }
+})
 </script>

BIN
src/packages/pc/assets/images/login-bg.jpg


BIN
src/packages/pc/assets/images/loginBackground.jpg


BIN
src/packages/pc/assets/images/logoBackground.jpg


BIN
src/packages/pc/assets/logo.png


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/packages/pc/assets/logo.svg


+ 1 - 1
src/packages/pc/assets/themes/default/variable.less

@@ -1,4 +1,4 @@
-:root {
+[theme='default'] {
     /* 字体大小规范 */
     --font-x-large            : 18px;
     --font-large              : 16px;

+ 42 - 21
src/packages/pc/components/base/table/index.less

@@ -1,16 +1,20 @@
 .app-table {
     &__header {
-        margin-bottom  : 20px;
-        display        : flex;
-        justify-content: space-between;
-        align-items    : center;
+        display: flex;
+        align-items: center;
 
-        >div {
-            display    : flex;
+        >.block {
+            display: inline-flex;
             align-items: center;
+            gap: 10px;
+            margin-bottom: 10px;
 
-            >*:not(:first-child):last-child {
-                margin-left: 10px;
+            &:empty {
+                display: none;
+            }
+
+            &--right {
+                margin-left: auto;
             }
         }
     }
@@ -24,35 +28,52 @@
     }
 
     .el-table {
-        --el-table-bg-color              : transparent; //表格背景颜色
-        --el-table-text-color            : #000; //表格文字颜色
-        --el-table-border-color          : #ebeef5; // 表格边框颜色
-        --el-table-border                : 1px solid var(--el-table-border-color); // 表格边框
-        --el-table-header-bg-color       : #f1f5f9; // 表头背景颜色
-        --el-table-header-text-color     : #556772; // 表头文字颜色
-        --el-table-tr-bg-color           : var(--el-table-bg-color); // 表格行背景颜色
-        --el-table-row-hover-bg-color    : #f5f7fa; // 表格行鼠标经过背景色
+        --el-table-bg-color: transparent; //表格背景颜色
+        --el-table-text-color: #000; //表格文字颜色
+        --el-table-border-color: #ebeef5; // 表格边框颜色
+        --el-table-border: 1px solid var(--el-table-border-color); // 表格边框
+        --el-table-header-bg-color: #f1f5f9; // 表头背景颜色
+        --el-table-header-text-color: #556772; // 表头文字颜色
+        --el-table-tr-bg-color: var(--el-table-bg-color); // 表格行背景颜色
+        --el-table-row-hover-bg-color: #f5f7fa; // 表格行鼠标经过背景色
         --el-table-expanded-cell-bg-color: var(--el-table-bg-color); // 展开行背景颜色
-        --el-table-current-row-bg-color  : #f5f7fa; // 当前行高亮颜色
+        --el-table-current-row-bg-color: #f5f7fa; // 当前行高亮颜色
 
         width: 100%;
 
         th.el-table__cell {
             font-weight: normal;
-            padding    : 10px 0;
+            padding: 8px 0;
         }
 
         td.el-table__cell {
-            padding: 10px 0;
+            padding: 8px 0;
+
+            &.radio .el-radio__label {
+                display: none;
+            }
 
             .el-table__placeholder:empty {
                 display: none;
             }
         }
 
+        &-column {
+            &--selection {
+                &.single {
+                    .el-checkbox {
+                        display: none;
+                    }
+                }
+            }
+        }
+
         &__expanded-cell {
-            .app-auth__button {
-                text-align: right;
+            .app-action-menu {
+                position: sticky;
+                right: 5px;
+                display: table;
+                margin-left: auto;
             }
         }
     }

+ 12 - 11
src/packages/pc/components/base/table/index.ts

@@ -1,42 +1,43 @@
 import { ref, shallowRef } from 'vue'
 import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+import { ComposeTableOptions } from './interface'
 
 /**
- * 处理表格组件
+ * 组合表格函数
  * @param rowKey 表格主键key
  */
-export function useTable<T>(rowKey: keyof T) {
+export function useComposeTable<T>({ rowKey }: ComposeTableOptions<T>) {
     // 表格选中的行数据
-    const selectedRow = ref<T>();
+    const selectedRow = ref<T>()
 
     // 表格展开行,对应 row-key 中的值
-    const expandKeys = shallowRef<T[keyof T][]>([]);
+    const expandKeys = shallowRef<T[keyof T][]>([])
 
     // 右键菜单
     const contextmenu = ref({
         show: false,
         clientX: 0,
         clientY: 0,
-    });
+    })
 
     // 表格行点击事件
     const rowClick = (row: T) => {
-        const keyValue = row[rowKey];
+        const keyValue = row[rowKey]
         if (expandKeys.value.includes(keyValue)) {
-            expandKeys.value = [];
+            expandKeys.value = []
         } else {
-            expandKeys.value = [keyValue];
+            expandKeys.value = [keyValue]
         }
-        selectedRow.value = row;
+        selectedRow.value = row
     }
 
     // 表格右键事件
     const rowContextmenu = (row: T, column: TableColumnCtx<T>, event: MouseEvent) => {
         // 阻止浏览器右键
-        event.preventDefault();
+        event.preventDefault()
 
         // 表格选中的行数据
-        selectedRow.value = row;
+        selectedRow.value = row
 
         // 显示表格右键菜单
         contextmenu.value = {

+ 57 - 12
src/packages/pc/components/base/table/index.vue

@@ -1,29 +1,34 @@
 <template>
   <div class="app-table" v-loading="loading">
-    <div class="app-table__header" v-if="showHeader">
-      <div>
+    <div class="app-table__header">
+      <div class="block block--left">
         <slot name="header"></slot>
       </div>
-      <div>
-        <el-button-group>
-          <el-button icon="Refresh" @click="refresh" />
-          <el-button icon="Setting" @click="showTableSetting = true" />
-        </el-button-group>
+      <div class="block block--right" v-if="showToolbar">
+        <slot name="toolbar">
+          <el-button-group>
+            <el-button icon="Refresh" @click="refresh" />
+            <el-button icon="Setting" @click="showTableSetting = true" />
+          </el-button-group>
+        </slot>
       </div>
     </div>
     <div class="app-table__container">
-      <el-table ref="tableRef" v-bind="$attrs" highlight-current-row scrollbar-always-on border>
+      <el-table ref="tableRef" :header-cell-class-name="selectionType" v-bind="$attrs" highlight-current-row
+        scrollbar-always-on @row-click="onRowClick" @select="onSelect">
         <!-- 展开行 -->
         <el-table-column type="expand" v-if="$slots.expand">
           <template #default="{ row, $index }">
             <slot name="expand" :row="row" :index="$index"></slot>
           </template>
         </el-table-column>
+        <!-- 选择列 -->
+        <el-table-column type="selection" width="55" align="center" fixed v-if="selectionType" />
         <!-- 数据列 -->
         <template v-for="(item, index) in columns" :key="index">
-          <el-table-column :class-name="item.className" :align="item.align ?? 'left'" :min-width="120"
+          <el-table-column :class-name="item.className" :align="item.align ?? 'center'" :min-width="120"
             :width="item.width" :label="item.label" :prop="item.prop" :fixed="item.fixed || false"
-            :sortable="item.sortable" v-if="item.show">
+            :sortable="item.sortable" show-overflow-tooltip v-if="item.show ?? true">
             <template #default="{ row, $index }">
               <slot :name="item.prop" :row="row" :value="row[item.prop]" :index="$index">
                 {{ handleNoneValue(row[item.prop]) }}
@@ -51,13 +56,14 @@ export default defineComponent({
   components: {
     AppTableSetting
   },
-  emits: ['refresh', 'update:columns'],
+  emits: ['refresh', 'update:columns', 'rowClick', 'select'],
   props: {
     columns: {
       type: Array as PropType<Model.TableColumn[]>,
       default: () => ([])
     },
-    showHeader: {
+    // 是否显示头部工具栏
+    showToolbar: {
       type: Boolean,
       default: true
     },
@@ -65,20 +71,59 @@ export default defineComponent({
       type: Boolean,
       default: false
     },
+    // 选择列类型
+    selectionType: {
+      type: String as PropType<'single' | 'multiple'>,
+    }
   },
   setup(props, { emit, expose }) {
     const tableRef = shallowRef()
+    const tableRadio = shallowRef()
     const showTableSetting = shallowRef(false)
     const refresh = () => emit('refresh')
     const updateColumn = (value: Model.TableColumn[]) => emit('update:columns', value)
 
+    // 当某一行被勾选时触发的事件
+    const onSelect = (selection: unknown[], currentRow: unknown) => {
+      const el = tableRef.value
+      const rows = selection.filter((e) => {
+        if (props.selectionType === 'single') {
+          if (e === currentRow) {
+            el.setCurrentRow(currentRow) // 高亮行
+            return true
+          } else {
+            el.toggleRowSelection(e, false) // 单选取消其他选中的行
+            return false
+          }
+        }
+        return true
+      })
+      emit('select', rows, currentRow)
+    }
+
+    // 当某一行被点击时选中该行
+    const onRowClick = (row: unknown) => {
+      // if (props.selectionType) {
+      //   const el = tableRef.value
+      //   const selection = el.getSelectionRows()
+      //   const selected = selection.find((e: unknown) => e === row)
+      //   el.toggleRowSelection(row, selected ? false : true)
+      //   onSelect(el.getSelectionRows(), row)
+      // }
+      emit('rowClick', row)
+    }
+
+    // 暴露组件属性
     expose({
       elTable: tableRef
     })
 
     return {
       tableRef,
+      tableRadio,
       showTableSetting,
+      onSelect,
+      onRowClick,
       handleNoneValue,
       refresh,
       updateColumn,

+ 7 - 0
src/packages/pc/components/base/table/interface.ts

@@ -0,0 +1,7 @@
+/**
+ * 组合表格配置项
+ */
+export interface ComposeTableOptions<T> {
+    rowKey: keyof T;
+    columnKey?: string;
+}

+ 18 - 19
src/packages/pc/components/layouts/header/index.less

@@ -1,13 +1,13 @@
 .app-header {
-    display        : flex;
+    display: flex;
     justify-content: space-between;
-    align-items    : center;
-    min-height     : var(--statusbar-height);
-    color          : #525b65;
-    padding        : 0 20px;
+    align-items: center;
+    min-height: var(--statusbar-height);
+    color: #525b65;
+    padding: 0 20px;
 
     &__left {
-        display    : flex;
+        display: flex;
         align-items: center;
 
         .el-breadcrumb__inner {
@@ -16,18 +16,19 @@
     }
 
     &__right {
-        display    : flex;
+        display: flex;
         align-items: center;
 
         .iconbar {
-            display    : flex;
+            display: flex;
             align-items: center;
+            gap: 20px;
 
             [class^='g-icon'] {
                 cursor: pointer;
 
                 &:before {
-                    font-size: 18px;
+                    font-size: 22px;
                 }
             }
 
@@ -44,26 +45,24 @@
                     content: var(--icon-maximize);
                 }
             }
-
-            >* {
-                margin-right: 20px;
-            }
         }
 
         .user-dropdown {
+            margin-left: 20px;
+
             &__link {
-                display    : flex;
+                display: flex;
                 align-items: center;
                 font-weight: bold;
-                cursor     : pointer;
+                cursor: pointer;
             }
 
             .g-image--avatar {
-                width        : 32px;
-                height       : 32px;
+                width: 32px;
+                height: 32px;
                 border-radius: 50%;
-                font-size    : 0;
-                margin-right : 4px;
+                font-size: 0;
+                margin-right: 4px;
             }
         }
     }

+ 12 - 15
src/packages/pc/components/layouts/header/index.vue

@@ -14,24 +14,19 @@
         <div class="app-header__right">
             <slot name="right"></slot>
             <div class="iconbar">
-                <el-badge type="danger" is-dot>
-                    <span class="g-icon--notice"></span>
-                </el-badge>
                 <span class="g-icon--minimize" @click="exitFullSreen" v-if="fullScreen"></span>
                 <span class="g-icon--maximize" @click="setFullSreen" v-else></span>
             </div>
             <el-dropdown class="user-dropdown" trigger="click">
                 <span class="user-dropdown__link">
-                    <img class="g-image--avatar" :src="admin.avatar" :title="admin.realName" />
-                    <span v-if="!state.isMobile">{{ admin.realName }}</span>
-                    <el-icon class="el-icon--right">
-                        <arrow-down />
-                    </el-icon>
+                    <img class="g-image--avatar" :title="getAccountName()" />
+                    <span v-if="!state.isMobile">{{ getAccountName() }}</span>
+                    <app-icon class="el-icon--right" icon="ArrowDown" />
                 </span>
                 <template #dropdown>
                     <el-dropdown-menu>
-                        <el-dropdown-item :icon="Key">修改密码</el-dropdown-item>
-                        <el-dropdown-item :icon="SwitchButton" @click="logout">退出登录</el-dropdown-item>
+                        <el-dropdown-item :icon="SwitchButton"
+                            @click="eventBus.$emit('LogoutNotify')">退出登录</el-dropdown-item>
                     </el-dropdown-menu>
                 </template>
             </el-dropdown>
@@ -41,13 +36,15 @@
 
 <script lang="ts" setup>
 import { ref, onMounted } from 'vue'
-import { ArrowRight, Key, SwitchButton, ArrowDown } from '@element-plus/icons-vue'
+import { ArrowRight, SwitchButton } from '@element-plus/icons-vue'
+import { useUserStore } from '@/stores'
+import eventBus from '@/services/bus'
 import client from '@/utils/client'
-import { useAdmin } from '@/business/admin'
+import AppIcon from '@pc/components/base/icon/index.vue'
 
-const { state } = client;
-const { admin, logout } = useAdmin();
-const fullScreen = ref(false);
+const { state } = client
+const { getAccountName } = useUserStore()
+const fullScreen = ref(false)
 
 // 全屏
 const setFullSreen = () => {

+ 43 - 41
src/packages/pc/components/layouts/navbar/index.vue

@@ -68,51 +68,53 @@ const removeTab = (index: number) => {
 // 滚动标签
 const scrollTab = () => {
     setTimeout(() => {
-        const el = {
-            container: containerElement.value,
-            tabbar: tabbarElement.value,
-            activeTab: containerElement.value.querySelector('li.is-active'),
-            lastTab: containerElement.value.querySelector('li:last-child')
-        }
-
-        if (el.tabbar.scrollWidth > el.container.clientWidth) {
-            showArrow.value = true;
-            // 等待箭头元素出现后再计算,不然可能出现计算误差
-            setTimeout(() => {
-                const rect = {
-                    container: el.container.getBoundingClientRect(), // 外层容器
-                    tabbar: el.tabbar.getBoundingClientRect(), // 标签栏
-                    activeTab: el.activeTab?.getBoundingClientRect(), // 标签栏中被选中的标签
-                    lastTab: el.lastTab?.getBoundingClientRect() // 标签栏中最后一个标签
-                }
-
-                if (rect.activeTab && rect.lastTab) {
-                    let tabbarOffset = rect.container.left - rect.tabbar.left, // 计算标签栏偏移容器距离
-                        activeTabOffsetLeft = rect.container.left - rect.activeTab.left, // 计算标签偏移容器左边的距离
-                        activeTabOffsetRight = rect.activeTab.right - rect.container.right; // 计算标签偏移容器右边的距离
-
-                    // 计算最后一个标签和容器最右边之间的距离
-                    const lastOffset = rect.container.right - rect.lastTab.right;
-                    if (activeTabOffsetLeft < lastOffset) {
-                        activeTabOffsetLeft += lastOffset - activeTabOffsetLeft;
-                    }
+        if (containerElement.value) {
+            const el = {
+                container: containerElement.value,
+                tabbar: tabbarElement.value,
+                activeTab: containerElement.value.querySelector('li.is-active'),
+                lastTab: containerElement.value.querySelector('li:last-child')
+            }
 
-                    // 判断标签是否超出父元素左边界
-                    if (activeTabOffsetLeft > 0) {
-                        const scrollX = tabbarOffset - activeTabOffsetLeft;
-                        el.tabbar.style.transform = 'translate3d(-' + scrollX + 'px,0,0)';
+            if (el.tabbar.scrollWidth > el.container.clientWidth) {
+                showArrow.value = true;
+                // 等待箭头元素出现后再计算,不然可能出现计算误差
+                setTimeout(() => {
+                    const rect = {
+                        container: el.container.getBoundingClientRect(), // 外层容器
+                        tabbar: el.tabbar.getBoundingClientRect(), // 标签栏
+                        activeTab: el.activeTab?.getBoundingClientRect(), // 标签栏中被选中的标签
+                        lastTab: el.lastTab?.getBoundingClientRect() // 标签栏中最后一个标签
                     }
 
-                    // 判断标签是否超出父元素右边界
-                    if (activeTabOffsetRight > 0) {
-                        const scrollX = tabbarOffset + activeTabOffsetRight;
-                        el.tabbar.style.transform = 'translate3d(-' + scrollX + 'px,0,0)';
+                    if (rect.activeTab && rect.lastTab) {
+                        let tabbarOffset = rect.container.left - rect.tabbar.left, // 计算标签栏偏移容器距离
+                            activeTabOffsetLeft = rect.container.left - rect.activeTab.left, // 计算标签偏移容器左边的距离
+                            activeTabOffsetRight = rect.activeTab.right - rect.container.right; // 计算标签偏移容器右边的距离
+
+                        // 计算最后一个标签和容器最右边之间的距离
+                        const lastOffset = rect.container.right - rect.lastTab.right;
+                        if (activeTabOffsetLeft < lastOffset) {
+                            activeTabOffsetLeft += lastOffset - activeTabOffsetLeft;
+                        }
+
+                        // 判断标签是否超出父元素左边界
+                        if (activeTabOffsetLeft > 0) {
+                            const scrollX = tabbarOffset - activeTabOffsetLeft;
+                            el.tabbar.style.transform = 'translate3d(-' + scrollX + 'px,0,0)';
+                        }
+
+                        // 判断标签是否超出父元素右边界
+                        if (activeTabOffsetRight > 0) {
+                            const scrollX = tabbarOffset + activeTabOffsetRight;
+                            el.tabbar.style.transform = 'translate3d(-' + scrollX + 'px,0,0)';
+                        }
                     }
-                }
-            }, 0)
-        } else {
-            showArrow.value = false;
-            el.tabbar.style.transform = 'translate3d(0,0,0)';
+                }, 0)
+            } else {
+                showArrow.value = false;
+                el.tabbar.style.transform = 'translate3d(0,0,0)';
+            }
         }
     }, 0)
 }

+ 41 - 36
src/packages/pc/components/layouts/sidebar/index.less

@@ -1,34 +1,34 @@
 .app-sidebar {
     &__view {
-        display       : flex;
+        display: flex;
         flex-direction: column;
-        min-height    : 100%;
-        color         : #fff;
-        background    : var(--sidebar-background);
+        min-height: 100%;
+        color: #fff;
+        background: var(--sidebar-background);
     }
 
     &__header,
     &__copyright {
-        display    : inline-flex;
-        width      : 0;
-        height     : 0;
+        display: inline-flex;
+        width: 0;
+        height: 0;
         white-space: nowrap;
-        overflow   : hidden;
-        transition : all 300ms;
+        overflow: hidden;
+        transition: all 300ms;
 
         &.is-hide {
-            border : 0;
+            border: 0;
             padding: 0;
         }
     }
 
     &__header {
-        align-items  : center;
-        padding      : 0 20px;
+        align-items: center;
+        padding: 0 20px;
         border-bottom: var(--sidebar-header-border);
 
         &:not(.is-hide) {
-            width : var(--sidebar-width);
+            width: var(--sidebar-width);
             height: var(--sidebar-header-height);
         }
 
@@ -39,12 +39,12 @@
 
     &__copyright {
         flex-direction: column;
-        font-size     : 12px;
-        height        : auto;
-        line-height   : 20px;
-        color         : #697b8f;
-        padding       : 10px;
-        margin-top    : auto;
+        font-size: 12px;
+        height: auto;
+        line-height: 20px;
+        color: #697b8f;
+        padding: 10px;
+        margin-top: auto;
 
         &:not(.is-hide) {
             width: var(--sidebar-width);
@@ -52,7 +52,7 @@
 
         a {
             display: block;
-            color  : inherit;
+            color: inherit;
         }
     }
 
@@ -60,7 +60,7 @@
         --el-menu-item-height: 48px;
 
         .el-menu {
-            border          : 0;
+            border: 0;
             background-color: transparent;
 
             &:not(&--collapse) {
@@ -74,10 +74,10 @@
             &--collapse {
                 .menu-icon {
                     &--text {
-                        display       : inline-block;
+                        display: inline-block;
                         vertical-align: middle;
-                        width         : 24px;
-                        text-align    : center;
+                        width: 24px;
+                        text-align: center;
                     }
                 }
 
@@ -100,19 +100,24 @@
 
             .el-sub-menu__title,
             .el-menu-item {
-                color    : var(--sidebar-menu-item);
+                color: var(--sidebar-menu-item);
                 min-width: auto;
 
                 &:hover {
-                    color           : #fff;
+                    color: #fff;
                     background-color: var(--sidebar-menu-item-hover);
                 }
+
+                &.is-active {
+                    color: #fff;
+                    background-color: var(--sidebar-menu-item-active);
+                }
             }
 
             .el-sub-menu {
                 .menu-icon {
                     font-size: inherit;
-                    color    : inherit;
+                    color: inherit;
 
                     &--text {
                         font-style: normal;
@@ -125,7 +130,7 @@
                     }
 
                     .el-menu-item.is-active {
-                        color     : #fff;
+                        color: #fff;
                         box-shadow: inset 2px 0 0 0 var(--sidebar-menu-item-active);
                     }
                 }
@@ -135,8 +140,8 @@
 
     /* 适配小屏设备 */
     [screen='small'] & {
-        position  : fixed;
-        z-index   : 2000;
+        position: fixed;
+        z-index: 2000;
         transition: transform 200ms;
 
         &.is-hide {
@@ -144,12 +149,12 @@
         }
 
         &__mask {
-            position        : fixed;
-            top             : 0;
-            left            : 0;
-            z-index         : -1;
-            width           : 100%;
-            height          : 100%;
+            position: fixed;
+            top: 0;
+            left: 0;
+            z-index: -1;
+            width: 100%;
+            height: 100%;
             background-color: rgba(0, 0, 0, .35);
 
             &.is-hide {

+ 2 - 3
src/packages/pc/components/layouts/sidebar/index.vue

@@ -1,14 +1,13 @@
 <template>
   <el-scrollbar :class="['app-sidebar', collapse && 'is-hide']" view-class="app-sidebar__view">
     <div :class="['app-sidebar__header', state.isMobile ? 'is-show' : collapse && 'is-hide']">
-      <span class="logo">{{ $t('app.name') }}</span>
+      <span class="logo">铁合金掌上行</span>
     </div>
     <div class="app-sidebar__menu">
       <app-sidemenu :collapse="state.isMobile ? false : collapse" @select="routerTo" />
     </div>
     <div :class="['app-sidebar__copyright', state.isMobile ? 'is-show' : collapse && 'is-hide']">
-      <a href="https://github.com/oringecat/vcat-admin" target="_blank">VCat Admin - GitHub</a>
-      <span>&copy;2007-{{ year }} teamwei.com</span>
+      <span>&copy;{{ year }} Muchinfo</span>
     </div>
     <div :class="['app-sidebar__mask', collapse && 'is-hide']" @click="hideSidebar()" v-if="state.isMobile"></div>
   </el-scrollbar>

+ 47 - 0
src/packages/pc/components/modules/action-menu/index.less

@@ -0,0 +1,47 @@
+.app-action-menu {
+    &__contextmenu {
+        position: fixed;
+        z-index: 1000;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+
+        ul {
+            position: absolute;
+            border: 1px solid #e4e7ed;
+            border-radius: 4px;
+            background-color: #fff;
+            box-shadow: 0 0 12px 0 rgba(0, 0, 0, .12);
+            overflow: hidden;
+            padding: 10px 0;
+
+            li {
+                display: flex;
+                align-items: center;
+                color: var(--el-text-color-regular);
+                cursor: pointer;
+                padding: 10px 24px;
+
+                &:hover {
+                    color: var(--el-color-primary);
+                    background-color: var(--el-color-primary-light-9);
+                }
+
+                .el-icon {
+                    margin-right: 5px;
+                }
+            }
+        }
+    }
+
+    &__button {
+        ul {
+            display: inline-flex;
+
+            li:not(:first-child) {
+                margin-left: 10px;
+            }
+        }
+    }
+}

+ 52 - 0
src/packages/pc/components/modules/action-menu/index.ts

@@ -0,0 +1,52 @@
+import { shallowRef, computed } from 'vue'
+import { useMenu } from '@/hooks/menu'
+import { ActionMenu, ActionMenuOptions } from './interface'
+
+export function useActionMenu<T>(options: ActionMenuOptions = {}) {
+    const { authCode, preventDefault = true, queryFn } = options
+    const { componentMap, getAuthButtons } = useMenu(authCode)
+    const authButtons = getAuthButtons()
+
+    // 当前激活的菜单
+    const activeMenu = shallowRef<Partial<ActionMenu<T>>>({})
+
+    // 当前打开的组件
+    const asyncComponent = computed(() => {
+        const { name = '' } = activeMenu.value
+        return componentMap.get(name)
+    })
+
+    const getActionMenus = (...filtered: string[]) => {
+        if (filtered.length) {
+            return authButtons.filter((e) => filtered.includes(e.code))
+        }
+        return authButtons
+    }
+
+    // 打开组件
+    const openComponent = (e: ActionMenu<T>) => {
+        if (preventDefault) {
+            e.disabled = true
+        }
+        activeMenu.value = e
+    }
+
+    // 关闭组件
+    const closeComponent = (refresh: boolean) => {
+        const name = activeMenu.value.name
+        if (preventDefault) {
+            activeMenu.value.disabled = false
+        }
+        activeMenu.value = {}
+        refresh && queryFn && queryFn(name)
+    }
+
+    return {
+        componentMap,
+        asyncComponent,
+        activeMenu,
+        getActionMenus,
+        openComponent,
+        closeComponent,
+    }
+}

+ 125 - 0
src/packages/pc/components/modules/action-menu/index.vue

@@ -0,0 +1,125 @@
+<template>
+    <div class="app-action-menu">
+        <teleport to="body" v-if="type === 'contextmenu'">
+            <div class="app-action-menu__contextmenu" @contextmenu.prevent.capture @click="closeContextmenu"
+                v-show="contextmenuOptions.show">
+                <ul :style="styles">
+                    <li :class="item.className" v-for="(item, index) in actionMenus" :key="index"
+                        @click.stop="onClick(item, index)">
+                        <el-icon v-if="item.icon">
+                            <component :is="item.icon" />
+                        </el-icon>
+                        <span>{{ item.label }}</span>
+                    </li>
+                </ul>
+            </div>
+        </teleport>
+        <div class="app-action-menu__dropdown" v-else-if="type === 'dropdown'">
+            <el-dropdown ref="dropdownRef" trigger="contextmenu" v-if="actionMenus.length > 1">
+                <el-button-group>
+                    <el-button size="small" round @click="onClick(actionMenus[0], 0)">{{
+                        actionMenus[0].label
+                    }}</el-button>
+                    <el-button size="small" icon="ArrowDownBold" round @click="dropdownRef.handleOpen()" />
+                </el-button-group>
+                <template #dropdown>
+                    <el-dropdown-menu>
+                        <template v-for="(item, index) in actionMenus" :key="index">
+                            <el-dropdown-item :class="item.className" :icon="item.icon"
+                                @click.stop="onClick(item, index)" v-if="index > 0">
+                                {{ item.label }}
+                            </el-dropdown-item>
+                        </template>
+                    </el-dropdown-menu>
+                </template>
+            </el-dropdown>
+            <el-button size="small" round @click="onClick(actionMenus[0], 0)" v-else-if="actionMenus.length">{{
+                actionMenus[0].label
+            }}</el-button>
+        </div>
+        <div class="app-action-menu__btnbar" v-else>
+            <template v-for="(item, index) in actionMenus" :key="index">
+                <el-button :class="item.className" :type="item.type" :disabled="item.disabled"
+                    @click.stop="onClick(item, index)" :link="linkButton">
+                    <el-icon v-if="item.icon">
+                        <component :is="item.icon" />
+                    </el-icon>
+                    <span v-if="item.label">{{ item.label }}</span>
+                </el-button>
+            </template>
+        </div>
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, shallowRef, computed, PropType, watchEffect } from 'vue'
+import { ActionMenu } from './interface'
+
+const props = defineProps({
+    // 操作菜单
+    menus: {
+        type: Array as PropType<Model.UserMenu[]>,
+        required: true,
+    },
+    // 操作类型
+    type: {
+        type: String as PropType<'button' | 'dropdown' | 'contextmenu'>,
+        default: 'button',
+    },
+    // 右键菜单
+    contextmenu: {
+        type: Object as PropType<{ show: boolean; clientX: number; clientY: number; }>,
+        default: () => ({
+            show: false,
+            clientX: 0,
+            clientY: 0,
+        }),
+    },
+    // 链接按钮
+    linkButton: {
+        type: Boolean,
+        default: false,
+    },
+    record: Object,
+})
+
+const emit = defineEmits(['click'])
+const contextmenuOptions = shallowRef(props.contextmenu)
+const dropdownRef = ref()
+const actionMenus = ref<ActionMenu[]>([])
+
+// 右键菜单坐标
+const styles = computed(() => {
+    const { clientX, clientY } = contextmenuOptions.value
+    return {
+        left: clientX + 'px',
+        top: clientY + 'px',
+    }
+})
+
+// 关闭右键菜单
+const closeContextmenu = () => {
+    contextmenuOptions.value.show = false
+}
+
+const onClick = (item: ActionMenu, index: number) => {
+    item.record = props.record
+    closeContextmenu()
+    emit('click', item, index)
+}
+
+watchEffect(() => {
+    actionMenus.value = props.menus.map((e) => ({
+        name: e.code,
+        label: e.title,
+        icon: e.icon,
+        className: e.className,
+        hide: e.hidden,
+    }))
+    contextmenuOptions.value = props.contextmenu
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 16 - 0
src/packages/pc/components/modules/action-menu/interface.ts

@@ -0,0 +1,16 @@
+export interface ActionMenu<T = unknown> {
+    name: string;
+    label: string;
+    type?: string;
+    className?: string;
+    icon?: string;
+    disabled?: boolean;
+    hide?: boolean;
+    record?: T;
+}
+
+export interface ActionMenuOptions {
+    authCode?: string;
+    queryFn?: (name?: string) => void;
+    preventDefault?: boolean; // 阻止按钮事件重复提交
+}

+ 2 - 2
src/packages/pc/components/modules/auth-component/index.vue

@@ -28,7 +28,7 @@ const props = defineProps({
     },
 })
 
-const { componentMap, getAuthComponent } = useMenu(props.code)
+const { componentMap, getAuthComponents } = useMenu(props.code)
 const { onChange } = useAttrs()
 const dataList = shallowRef<Model.UserMenu[]>([]) // 数据列表
 const dataIndex = shallowRef(0) // 选中的标签
@@ -44,7 +44,7 @@ const onTabChange = (index: number, { code }: Model.UserMenu) => {
 }
 
 onMounted(() => {
-    const auth = getAuthComponent();
+    const auth = getAuthComponents();
 
     if (props.tabs.length) {
         dataList.value = auth.filter((e) => props.tabs.includes(e.code))

+ 16 - 16
src/packages/pc/components/modules/auth-operation/index.less

@@ -1,30 +1,30 @@
 .app-auth {
     &__contextmenu {
         position: fixed;
-        z-index : 1000;
-        top     : 0;
-        left    : 0;
-        width   : 100%;
-        height  : 100%;
+        z-index: 1000;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
 
         ul {
-            position        : absolute;
-            border          : 1px solid #e4e7ed;
-            border-radius   : 4px;
+            position: absolute;
+            border: 1px solid #e4e7ed;
+            border-radius: 4px;
             background-color: #fff;
-            box-shadow      : 0 0 12px 0 rgba(0, 0, 0, .12);
-            overflow        : hidden;
-            padding         : 10px 0;
+            box-shadow: 0 0 12px 0 rgba(0, 0, 0, .12);
+            overflow: hidden;
+            padding: 10px 0;
 
             li {
-                display    : flex;
+                display: flex;
                 align-items: center;
-                color      : var(--el-text-color-regular);
-                cursor     : pointer;
-                padding    : 10px 24px;
+                color: var(--el-text-color-regular);
+                cursor: pointer;
+                padding: 10px 24px;
 
                 &:hover {
-                    color           : var(--el-color-primary);
+                    color: var(--el-color-primary);
                     background-color: var(--el-color-primary-light-9);
                 }
 

+ 11 - 13
src/packages/pc/components/modules/auth-operation/index.vue

@@ -16,8 +16,8 @@
             </div>
         </teleport>
         <div class="app-auth__button" v-else>
-            <el-dropdown v-if="type === 'dropdown'">
-                <el-button type="info" icon="Setting" />
+            <el-dropdown trigger="click" v-if="type === 'dropdown'">
+                <el-button size="small" icon="MoreFilled" round />
                 <template #dropdown>
                     <el-dropdown-menu>
                         <el-dropdown-item :class="item.code" v-for="(item, index) in dataList" :key="index"
@@ -29,7 +29,7 @@
             </el-dropdown>
             <ul v-else>
                 <li v-for="(item, index) in dataList" :key="index">
-                    <el-button :class="item.code" :type="item.buttonType" :disabled="item.code === componentId"
+                    <el-button :class="item.className" :type="item.buttonType" :disabled="item.code === componentId"
                         @click="openComponent(item.code)" :link="linkButton">
                         <el-icon v-if="item.icon">
                             <component :is="item.icon" />
@@ -45,7 +45,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, onDeactivated, shallowRef, PropType, computed, watch } from 'vue'
+import { onMounted, onUnmounted, shallowRef, PropType, computed, watch } from 'vue'
 import { useMenu } from '@/hooks/menu'
 
 const emit = defineEmits(['click', 'closed'])
@@ -54,6 +54,8 @@ const props = defineProps({
     code: String,
     // 组件参数
     options: Object,
+    // 菜单列表
+    menus: Array as PropType<string[]>,
     // 操作类型
     type: {
         type: String as PropType<'button' | 'dropdown' | 'contextmenu'>,
@@ -73,21 +75,18 @@ const props = defineProps({
         type: Boolean,
         default: false,
     },
-    menus: {
-        type: Array as PropType<string[]>,
-        default: () => ([]),
-    },
 })
 
-const { componentMap, getAuthButton } = useMenu(props.code);
+const { componentMap, getAuthButtons } = useMenu(props.code);
 const componentId = shallowRef<string>();
 const contextmenuOption = shallowRef(props.contextmenu);
 const auth = shallowRef<Model.UserMenu[]>([]);
 
 // 数据列表
 const dataList = computed(() => {
-    if (props.menus.length) {
-        return auth.value.filter((e) => props.menus.includes(e.code) || props.menus.includes(e.buttonName));
+    const menus = props.menus
+    if (menus) {
+        return auth.value.filter((e) => menus.includes(e.code) || menus.includes(e.buttonName));
     } else {
         return auth.value;
     }
@@ -122,9 +121,8 @@ const closeComponent = (isCallback?: boolean) => {
 
 watch(() => props.contextmenu, (val) => contextmenuOption.value = val)
 
-onMounted(() => auth.value = getAuthButton())
+onMounted(() => auth.value = getAuthButtons())
 onUnmounted(() => closeComponent())
-onDeactivated(() => closeComponent())
 </script>
 
 <style lang="less">

+ 0 - 4
src/packages/pc/main.ts

@@ -5,7 +5,6 @@ import directives from '@/directives' // 自定义指令集
 import '@/services/subscribe' // 全局订阅通知
 import '@/mock' // 模拟数据
 import client from '@/utils/client' // 适配客户端
-//import { useLanguageStore } from '@/stores' // 国际化语言
 import layouts from "./components/layouts" // 布局组件
 import ElementPlus from 'element-plus'
 import * as ElementIcons from '@element-plus/icons-vue'
@@ -13,14 +12,11 @@ import 'element-plus/dist/index.css'
 import './assets/themes/style.less' // 主题样式
 import { timerInterceptor } from '@/utils/timer'
 
-//const { i18n } = useLanguageStore()
-
 const app = createApp(App)
 app.use(router)
 app.use(directives)
 app.use(ElementPlus)
 app.use(layouts)
-//app.use(i18n)
 app.mount('#app')
 
 // 等待 html 加载完成

+ 1 - 0
src/packages/pc/router/dynamicRouter.ts

@@ -47,6 +47,7 @@ export default new (class {
                         title: item.title,
                         icon: item.icon,
                         remark: item.remark,
+                        keepAlive: true, // 默认缓存页面
                     },
                 }
 

+ 9 - 8
src/packages/pc/router/historyRouter.ts

@@ -43,7 +43,7 @@ export default new (class {
     create = (options: RouterOptions) => {
         const router = createRouter(options);
         const { push, replace, go, forward, back } = router;
-        const { actionName } = toRefs(this._state.value);
+        const { actionName, excludeViews } = toRefs(this._state.value);
 
         // 添加
         router.push = (to: RouteRecordRaw) => {
@@ -81,16 +81,17 @@ export default new (class {
         }
 
         router.beforeResolve((to) => {
-            this.addHistory({
-                name: to.name || to.path,
-                title: to.meta.title as string ?? '标签页',
-                fullPath: to.fullPath,
-                redirected: !!to.redirectedFrom,
-            });
+            if (to.meta.keepAlive) {
+                this.addHistory({
+                    name: to.name || to.path,
+                    title: to.meta.title as string ?? '标签页',
+                    fullPath: to.fullPath,
+                    redirected: !!to.redirectedFrom,
+                });
+            }
         })
 
         router.afterEach(() => {
-            const { excludeViews } = toRefs(this._state.value);
             excludeViews.value = [];
         })
 

+ 6 - 4
src/packages/pc/router/index.ts

@@ -9,14 +9,15 @@ const { getToken } = useLoginStore()
 const routes: Array<RouteRecordRaw> = [
     {
         path: '/',
-        redirect: () => getToken() ? '/home/main' : '/login', // 重定向到默认页面
+        redirect: () => getToken() ? '/member' : '/login', // 重定向到默认页面
     },
     {
         path: '/login',
         name: 'login',
-        component: () => import('../views/user/login/index.vue'),
+        component: () => import('../views/auth/login/index.vue'),
         meta: {
             title: "登陆",
+            keepAlive: false
         },
     },
     {
@@ -24,7 +25,8 @@ const routes: Array<RouteRecordRaw> = [
         name: 'boot',
         component: () => import('../views/boot/index.vue'),
         meta: {
-            title: "初始化",
+            title: '初始化',
+            keepAlive: false
         },
     },
 ]
@@ -66,7 +68,7 @@ router.beforeEach((to, from, next) => {
             }
         }
     } else {
-        if (to.name === 'boot') {
+        if (to.name === 'boot' || to.name === 'login') {
             next();
         } else {
             next({

+ 61 - 0
src/packages/pc/views/auth/components/layout/index.less

@@ -0,0 +1,61 @@
+.sign-layout {
+    display: flex;
+    height: 100%;
+
+    &__left {
+        flex: 1;
+        background: url("~@pc/assets/images/login-bg.jpg") no-repeat center center;
+        background-size: cover;
+    }
+
+    &__right {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 600px;
+        background-color: #fff;
+        padding: 0 110px;
+
+        .login {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            padding: 0 55px;
+
+            &-logo {
+                text-align: center;
+                padding: 150px 0 55px 0;
+
+                img {
+                    width: 72px;
+                    height: 72px;
+                }
+            }
+
+            &-container {
+                flex: 1;
+                width: 260px;
+
+                &__title {
+                    font-size: 26px;
+                    font-weight: bold;
+                    margin-bottom: 20px;
+                }
+
+                .el-button.submit {
+                    width: 100%;
+                    height: 40px;
+                }
+            }
+
+            &-footer {
+                display: flex;
+                flex-direction: column;
+                font-size: 12px;
+                text-align: center;
+                padding: 20px 0;
+            }
+        }
+    }
+}

+ 29 - 0
src/packages/pc/views/auth/components/layout/index.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="sign-layout">
+    <div class="sign-layout__left" />
+    <div class="sign-layout__right">
+      <div class="login-logo">
+        <img src="@pc/assets/logo.svg" title="logo" />
+      </div>
+      <div class="login-container">
+        <div class="login-container__title">{{ title }}</div>
+        <slot></slot>
+      </div>
+      <div class="login-footer">
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+defineProps({
+  title: {
+    type: String,
+    required: true,
+  },
+})
+</script>
+
+<style lang="less">
+@import './index.less';
+</style>

+ 0 - 0
src/packages/pc/views/user/login/index.less → src/packages/pc/views/auth/login/index.less


+ 13 - 6
src/packages/pc/views/user/login/index.vue → src/packages/pc/views/auth/login/index.vue

@@ -9,10 +9,10 @@
         </el-input>
       </el-form-item>
       <el-form-item>
-        <el-checkbox label="记住账号"></el-checkbox>
+        <el-checkbox label="记住账号" v-model="remember"></el-checkbox>
       </el-form-item>
       <el-form-item>
-        <el-button class="submit" type="primary" :loading="loading" @click="userLogin">
+        <el-button class="submit" type="primary" :loading="loading" @click="formSubmit">
           <span v-if="loading">正在登录</span>
           <span v-else>登录</span>
         </el-button>
@@ -23,17 +23,21 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
+import { shallowRef } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { useAuth } from '@/business/auth'
+import { useMenuStore } from '@/stores'
 import SignLayout from '../components/layout/index.vue'
 
 const { loading, user, login } = useAuth()
+const { getUserMenuList } = useMenuStore()
 const route = useRoute()
 const router = useRouter()
-const formRef = ref<FormInstance>()
+const formRef = shallowRef<FormInstance>()
+const remember = shallowRef(false) // 记住账号
+
 const formRules: FormRules = {
   LoginID: [
     { required: true, max: 20, message: '随便输入', trigger: 'blur' }
@@ -43,10 +47,13 @@ const formRules: FormRules = {
   ]
 }
 
-const userLogin = () => {
+const formSubmit = () => {
   formRef.value?.validate((valid) => {
     if (valid) {
-      login().then(() => {
+      login().then(async () => {
+        await getUserMenuList().catch(() => {
+          loading.value = false
+        })
         const redirect = route.query.redirect;
         if (redirect) {
           router.replace(redirect.toString());

+ 60 - 0
src/packages/pc/views/bill/index.vue

@@ -0,0 +1,60 @@
+<template>
+    <app-view class="bill">
+        <app-table :data="dataList" v-model:columns="tableColumns" :loading="loading">
+            <template #footer>
+                <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                    @change="getTHJProfits" />
+            </template>
+        </app-table>
+    </app-view>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { useDataTable } from '@/hooks/datatable'
+import { queryTHJProfits } from '@/services/api/common'
+import { useLoginStore } from '@/stores'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+
+const { getUserId } = useLoginStore()
+const { dataList, total, pageIndex, pageSize } = useDataTable<Model.THJProfitsRsp>()
+const loading = shallowRef(false)
+
+const tableColumns = shallowRef([
+    { prop: 'marketname', label: '市场' },
+    { prop: 'firend', label: '下单好友' },
+    { prop: 'buyorselldisplay', label: '方向' },
+    { prop: 'wrstandardname', label: '商品' },
+    { prop: 'tradeqty', label: '成交数量' },
+    { prop: 'chargeamount', label: '手续费总额' },
+    { prop: 'profitamount', label: '分润金额' },
+    { prop: 'profitroletypedisplay', label: '分润角色' },
+    { prop: 'brokerrate', label: '会员比例' },
+    { prop: 'brokerprofitrate', label: '会员释出比例' },
+    { prop: 'levelonevalue', label: '一级比例' },
+    { prop: 'leveltwovalue', label: '二级比例' },
+    { prop: 'tradeid', label: '成交单号' },
+    { prop: 'tradetimedisplay', label: '成交时间' },
+])
+
+const getTHJProfits = () => {
+    loading.value = true
+    queryTHJProfits({
+        data: {
+            userid: getUserId(),
+            page: pageIndex.value,
+            pagesize: pageSize.value,
+        },
+        success: (res) => {
+            total.value = res.total
+            dataList.value = res.data
+        },
+        complete: () => {
+            loading.value = false
+        }
+    })
+}
+
+getTHJProfits()
+</script>

+ 26 - 35
src/packages/pc/views/boot/index.vue

@@ -1,57 +1,48 @@
 <template>
-  <div class="boot">
-    <el-button :loading="loading" v-if="loading">正在烧烤...</el-button>
-    <el-button @click="initService" v-else>重新烧烤</el-button>
-  </div>
+  <div class="boot" v-loading="loading"></div>
 </template>
 
 <script lang="ts" setup>
 import { ref } from 'vue'
-import { ElMessage } from 'element-plus'
 import { useRoute, useRouter } from 'vue-router'
-import { initBaseData } from '@/business/common'
-import { useLoginStore } from '@/stores'
+import { initBaseData, checkToken, checkTokenLoop } from '@/business/common'
+import { useEnumStore, useErrorInfoStore } from '@/stores'
 import service from '@/services'
 import socket from '@/services/socket'
 
-const { getToken } = useLoginStore()
 const route = useRoute()
 const router = useRouter()
-const loading = ref(false)
-
-const initService = async () => {
-  loading.value = true
+const { getAllEnumList } = useEnumStore()
+const { getErrorInfoList } = useErrorInfoStore()
+const loading = ref(true)
 
+// 初始化数据
+const onLoad = (async () => {
   // 等待服务初始化
-  await service.onReady().catch((err) => {
-    ElMessage.error(err)
-    loading.value = false
-  })
-
-  if (getToken()) {
-    // 等待连接交易服务
-    await socket.connectTrade().catch((err) => {
-      ElMessage.error(err)
-      loading.value = false
-    })
-
-    // 等待业务数据初始化
-    await initBaseData().catch((err) => {
-      ElMessage.error(err)
-      loading.value = false
-    })
-  }
-
-  // 路由跳转
+  await service.onReady()
+  // 等待请求枚举
+  await getAllEnumList()
+  // 等待请求系统错误信息
+  await getErrorInfoList()
+  // 等待连接交易服务
+  await socket.connectTrade()
+  // 等待令牌效验
+  await checkToken()
+  // 等待业务数据初始化
+  await initBaseData()
+})()
+
+onLoad.then(() => {
+  checkTokenLoop()
   const redirect = route.query.redirect
   if (redirect) {
     router.replace(redirect.toString())
   } else {
     router.replace('/')
   }
-}
-
-initService()
+}).catch(() => {
+  router.replace('/login')
+})
 </script>
 
 <style lang="less" scoped>

+ 0 - 8
src/packages/pc/views/home/main/index.vue

@@ -1,8 +0,0 @@
-<template>
-    <app-view class="home-main">
-        首页
-    </app-view>
-</template>
-
-<script lang="ts" setup>
-</script>

+ 0 - 12
src/packages/pc/views/market/chart/components/detail/index.vue

@@ -1,12 +0,0 @@
-<template>
-    <app-view class="goods-detail">
-        商品详情
-        <el-input v-model="inputValue" />
-    </app-view>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue'
-
-const inputValue = ref('')
-</script>

+ 0 - 2
src/packages/pc/views/market/chart/index.less

@@ -1,2 +0,0 @@
-.futures-goods {
-}

+ 0 - 47
src/packages/pc/views/market/chart/index.vue

@@ -1,47 +0,0 @@
-<template>
-  <app-view class="futures-goods" @ready="onReady">
-    <app-tabs class="app-tabs--info" :data-list="chartCycleTypeList" :data-index="1" @change="tabChange">
-      <component :is="components.echartsTimeline" goodscode="cu2208" style="height:500px;"
-        v-if="selectedCycleType === ChartCycleType.Time" />
-      <component :is="components.echartsKline" goodscode="cu2208" :cycle-type="selectedCycleType" style="height:500px;"
-        v-else />
-    </app-tabs>
-  </app-view>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent, ref, onMounted, onUnmounted } from 'vue'
-import { timerInterceptor } from '@/utils/timer'
-import { ChartCycleType, getChartCycleTypeList } from '@/constants/chart'
-import subscribe from '@/services/subscribe'
-import AppTabs from '@/components/base/tabs/index.vue'
-
-const components = {
-  echartsKline: defineAsyncComponent(() => import('@pc/components/modules/echarts-kline/index.vue')),
-  echartsTimeline: defineAsyncComponent(() => import('@pc/components/modules/echarts-timeline/index.vue')),
-}
-
-const scrollEl = ref<HTMLDivElement>();
-const selectedCycleType = ref(ChartCycleType.Minutes); // 当前选中的图表周期
-const chartCycleTypeList = getChartCycleTypeList();
-const quoteSubscribe = subscribe.addQuoteSubscribe(['cu2208']); // 行情订阅
-
-// 指标切换
-const tabChange = (index: number) => {
-  selectedCycleType.value = chartCycleTypeList[index].value;
-}
-
-const onReady = () => {
-  const onscroll = timerInterceptor.setDebounce(() => {
-    console.log(new Date().getTime())
-  });
-  scrollEl.value?.addEventListener('scroll', onscroll)
-}
-
-onMounted(() => quoteSubscribe.start());
-onUnmounted(() => quoteSubscribe.stop());
-</script>
-
-<style lang="less" scoped>
-@import './index.less';
-</style>

+ 0 - 5
src/packages/pc/views/market/quote/components/detail/index.less

@@ -1,5 +0,0 @@
-.quote-detail {
-    .app-modal__container {
-        width: 50%;
-    }
-}

+ 0 - 31
src/packages/pc/views/market/quote/components/detail/index.vue

@@ -1,31 +0,0 @@
-<!-- 详情 -->
-<template>
-  <app-drawer class="quote-detail" title="详情" v-model:show="show">
-    <el-descriptions title="User Info" :column="1" style="margin-bottom: 20px;">
-      <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
-      <el-descriptions-item label="Telephone">18100000000</el-descriptions-item>
-      <el-descriptions-item label="Place" :span="2">Suzhou</el-descriptions-item>
-    </el-descriptions>
-    <el-descriptions title="Vertical list with border" direction="vertical" :column="4" border>
-      <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
-      <el-descriptions-item label="Telephone">18100000000</el-descriptions-item>
-      <el-descriptions-item label="Place" :span="2">Suzhou</el-descriptions-item>
-      <el-descriptions-item label="Remarks">
-        <el-tag size="small">School</el-tag>
-      </el-descriptions-item>
-      <el-descriptions-item label="Address">No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province
-      </el-descriptions-item>
-    </el-descriptions>
-  </app-drawer>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue'
-import AppDrawer from '../../../../../components/base/drawer/index.vue'
-
-const show = ref(true);
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 0 - 52
src/packages/pc/views/market/quote/components/edit/index.vue

@@ -1,52 +0,0 @@
-<!-- 编辑 -->
-<template>
-    <app-drawer class="quote-edit" title="编辑" width="55%" v-model:show="show">
-        <template #default>
-            <el-form>
-                <el-form-item label="商品名称">
-                    <el-input />
-                </el-form-item>
-                <el-form-item label="所属类别">
-                    <el-checkbox-group>
-                        <el-checkbox label="钻石" name="type" />
-                        <el-checkbox label="黄金" name="type" />
-                        <el-checkbox label="蓝宝石" name="type" />
-                        <el-checkbox label="银" name="type" />
-                    </el-checkbox-group>
-                </el-form-item>
-                <el-form-item>
-                    <app-editor />
-                </el-form-item>
-            </el-form>
-        </template>
-        <template #footer>
-            <el-button @click="cancel">取消</el-button>
-            <el-button @click="submit" type="primary">提交</el-button>
-        </template>
-    </app-drawer>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue'
-import { ElMessageBox } from 'element-plus'
-import AppDrawer from '@pc/components/base/drawer/index.vue'
-import AppEditor from '@pc/components/base/editor/index.vue'
-
-const show = ref(true);
-
-const cancel = () => {
-    ElMessageBox.confirm(
-        '编辑未保存,是否关闭?',
-        '提示',
-        {
-            type: 'warning',
-        }
-    ).then(() => {
-        show.value = false;
-    })
-}
-
-const submit = () => {
-    show.value = false;
-}
-</script>

+ 0 - 12
src/packages/pc/views/market/quote/components/futures/index.vue

@@ -1,12 +0,0 @@
-<template>
-    <div>这是期货动态组件:{{ code }}</div>
-</template>
-
-<script lang="ts" setup>
-defineProps({
-    code: {
-        type: String,
-        required: true
-    },
-})
-</script>

+ 0 - 16
src/packages/pc/views/market/quote/components/spot/index.vue

@@ -1,16 +0,0 @@
-<template>
-    <div>这是现货动态组件:{{ selectedRow }}</div>
-</template>
-
-<script lang="ts" setup>
-defineProps({
-    code: {
-        type: String,
-        required: true
-    },
-    selectedRow: {
-        type: Object,
-        default: () => ({})
-    }
-})
-</script>

+ 0 - 1
src/packages/pc/views/market/quote/index.less

@@ -1 +0,0 @@
-.market-quote {}

+ 0 - 121
src/packages/pc/views/market/quote/index.vue

@@ -1,121 +0,0 @@
-<template>
-  <app-view class="market-quote">
-    <template #header>
-      <app-filter :options="filterOptons" />
-    </template>
-    <!-- 表格数据 -->
-    <app-table :data="dataList" v-model:columns="columns" :row-key="rowKey" :expand-row-keys="expandKeys"
-      @row-click="rowClick" @row-contextmenu="rowContextmenu" border>
-      <template #header>
-        <app-auth-operation :menus="headerButtons" />
-      </template>
-      <template #footer>
-        <app-pagination :total="400" />
-      </template>
-      <!-- 展开行 -->
-      <template #expand="{ row }">
-        <app-auth-operation :menus="handleTableButton(row)" :options="{ selectedRow: row }" />
-      </template>
-    </app-table>
-    <!-- 右键菜单 -->
-    <app-auth-operation type="contextmenu" :menus="handleTableButton(selectedRow)" :contextmenu="contextmenu"
-      :options="{ selectedRow }" />
-  </app-view>
-</template>
-
-<script lang="ts" setup>
-import { ref, onActivated } from 'vue'
-import { useDataTable, useDataFilter } from '@/hooks/datatable'
-import { onBeforeRouteLeave } from 'vue-router'
-import { queryGoodsList } from '@/services/api/goods'
-import { useTable } from '@pc/components/base/table'
-import subscribe from '@/services/subscribe'
-import AppTable from '@pc/components/base/table/index.vue'
-import AppPagination from '@pc/components/base/pagination/index.vue'
-import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
-import AppFilter from '@pc/components/base/table-filter/index.vue'
-
-const { selectedRow, rowKey, expandKeys, contextmenu, rowClick, rowContextmenu } = useTable<Model.GoodsRsp>('id');
-const { dataList, filters } = useDataTable<Model.GoodsRsp>();
-const { filterOptons, getFilterParams } = useDataFilter<Model.GoodsRsp>();
-const quoteSubscribe = subscribe.addQuoteSubscribe(['cu2206', 'cu2207', 'cu2208', 'cu2209', 'cu2301', 'cu2303', 'cu2304']);
-const headerButtons = ['market_quote_add', 'market_quote_delete'];
-
-const columns = ref([
-  {
-    prop: 'id',
-    label: '序号',
-    sortable: true,
-    show: true,
-    fixed: '',
-  },
-  {
-    prop: 'goodsCode',
-    label: '合约',
-    sortable: true,
-    show: true,
-  },
-  {
-    prop: 'goodsName',
-    label: '商品',
-    show: true,
-  },
-  {
-    prop: 'lastPrice',
-    label: '最新价',
-    show: true,
-  }
-])
-
-// 处理权限按钮显示
-const handleTableButton = (item?: Model.GoodsRsp) => {
-  const buttons = ['market_quote_trade', 'market_quote_detail'];
-  switch (item?.goodsCode) {
-    case 'CFN001': {
-      return buttons.filter((code) => code !== 'market_quote_detail')
-    }
-    default: {
-      return buttons;
-    }
-  }
-}
-
-const onSearch = (clear = false) => {
-  getFilterParams((params) => {
-    filters.value = params
-  }, clear)
-}
-
-filterOptons.selectList = [
-  {
-    key: 'id',
-    placeholder: '商品合约',
-    options: [
-      { label: '黄金', value: 1000 },
-      { label: '昆特牌', value: 1001 }
-    ],
-  },
-]
-
-filterOptons.inputList = [
-  { placeholder: '模糊搜索商品名称', keys: ['goodsName', 'goodsCode'] },
-]
-
-filterOptons.buttonList = [
-  { lable: '重置', onClick: () => onSearch(true) },
-  { lable: '查询', className: 'el-button--primary', onClick: () => onSearch() }
-]
-
-queryGoodsList({
-  success: (res) => {
-    dataList.value = res.data;
-  }
-})
-
-onActivated(() => quoteSubscribe.start());
-onBeforeRouteLeave(() => quoteSubscribe.stop());
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 61 - 0
src/packages/pc/views/member/index.vue

@@ -0,0 +1,61 @@
+<template>
+    <el-container>
+        <el-aside>
+            <el-scrollbar>
+
+            </el-scrollbar>
+        </el-aside>
+        <el-main>
+            <el-scrollbar>
+                <app-table :data="dataList" v-model:columns="tableColumns" :loading="loading">
+                    <template #footer>
+                        <app-pagination :total="total" v-model:page-size="pageSize" v-model:page-index="pageIndex"
+                            @change="getTHJFriends" />
+                    </template>
+                </app-table>
+            </el-scrollbar>
+        </el-main>
+    </el-container>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef } from 'vue'
+import { useDataTable } from '@/hooks/datatable'
+import { queryTHJFriends } from '@/services/api/common'
+import { useLoginStore } from '@/stores'
+import AppTable from '@pc/components/base/table/index.vue'
+import AppPagination from '@pc/components/base/pagination/index.vue'
+
+const { getUserId } = useLoginStore()
+const { dataList, total, pageIndex, pageSize } = useDataTable<Model.THJFriendsRsp>()
+const loading = shallowRef(false)
+
+const tableColumns = shallowRef([
+    { prop: 'customername', label: '好友名称' },
+    { prop: 'mobile', label: '手机号' },
+    { prop: 'groupname', label: '客户等级' },
+    { prop: 'hasauth', label: '是否已实名' },
+    { prop: 'accoutstatus', label: '状态' },
+    { prop: 'createtime', label: '开户时间' },
+])
+
+const getTHJFriends = () => {
+    loading.value = true
+    queryTHJFriends({
+        data: {
+            userid: getUserId(),
+            page: pageIndex.value,
+            pagesize: pageSize.value,
+        },
+        success: (res) => {
+            total.value = res.total
+            dataList.value = res.data
+        },
+        complete: () => {
+            loading.value = false
+        }
+    })
+}
+
+getTHJFriends()
+</script>

+ 0 - 22
src/packages/pc/views/order/list/index.less

@@ -1,22 +0,0 @@
-.app-order {
-    width          : 100%;
-    border-collapse: collapse;
-
-    th,
-    td {
-        text-align: center;
-        border    : 1px solid #e7eaef;
-    }
-
-    th {
-        text-align: left;
-    }
-
-    &__header {
-        background-color: #ecf0f5;
-    }
-
-    &__spacing {
-        height: 16px;
-    }
-}

+ 0 - 101
src/packages/pc/views/order/list/index.vue

@@ -1,101 +0,0 @@
-<template>
-    <app-view>
-        <table class="app-order" v-if="tableList.length">
-            <tbody v-for="(item, i) in tableList" :key="i">
-                <tr class="app-order__header">
-                    <th class="column" colspan="5">
-                        <div class="g-order__topbar">
-                            <el-checkbox size="large" v-model="checkedAll" />
-                            <div class="label">订单号:</div>
-                            <div class="label">下单日期:</div>
-                        </div>
-                    </th>
-                </tr>
-                <tr class="app-order__body" v-for="(child, j) in item.orderItems" :key="j">
-                    <td class="column">
-                        <div class="g-order__item">
-                            <div class="gallery">
-                                商品图片
-                            </div>
-                            <div class="info">
-                                <div class="info-title">{{ child.goodsName }}</div>
-                                <div class="info-desc">{{ child.skuInof }}</div>
-                                <div class="info-desc" v-if="child.goodsCode">编码:{{ child.goodsCode }}</div>
-                            </div>
-                            <div class="amount">
-                                <span class="g-price--plus">{{ Number(child.amount).toFixed(2) }}</span>
-                                <span class="refund">售后状态</span>
-                            </div>
-                            <div class="quantity">
-                                <span>×{{ child.quantity }}</span>
-                            </div>
-                        </div>
-                    </td>
-                    <td class="column" :rowspan="item.orderItems.length" v-if="j == 0">
-                        <span>买家</span>
-                    </td>
-                    <td class="column" :rowspan="item.orderItems.length" v-if="j == 0">订单状态</td>
-                    <td class="column column--bold" :rowspan="item.orderItems.length" v-if="j == 0">
-                        <span class="g-price--plus">支付金额</span>
-                    </td>
-                    <td class="column" :rowspan="item.orderItems.length" v-if="j == 0">
-                        <el-button type="primary">发货</el-button>
-                        <el-button>查看</el-button>
-                    </td>
-                </tr>
-                <tr class="app-order__spacing" v-if="tableList.length > (i + 1)"></tr>
-            </tbody>
-        </table>
-    </app-view>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue'
-
-const checkedAll = ref(false)
-const tableList = ref([
-    {
-        id: 0,
-        orderNumber: '',
-        orderStatus: 0,
-        paymentAt: '',
-        createdAt: '',
-        orderItems: [
-            {
-                goodsCode: '',
-                goodsName: '',
-                skuInof: '',
-                amount: 0,
-                quantity: 0
-            },
-            {
-                goodsCode: '',
-                goodsName: '',
-                skuInof: '',
-                amount: 0,
-                quantity: 0
-            }
-        ]
-    },
-    {
-        id: 0,
-        orderNumber: '',
-        orderStatus: 0,
-        paymentAt: '',
-        createdAt: '',
-        orderItems: [
-            {
-                goodsCode: '',
-                goodsName: '',
-                skuInof: '',
-                amount: 0,
-                quantity: 0
-            }
-        ]
-    }
-])
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 13 - 29
src/packages/pc/views/system/menu/index.vue

@@ -2,17 +2,19 @@
     <app-view class="system-menu">
         <app-table ref="tableRef" :data="dataList" v-model:columns="columns" row-key="code" :selection="false">
             <template #header>
-                <app-auth-operation :menus="['system_menu_add']" />
+                <app-action-menu :menus="getAuthButtons(['system_menu_add'])" @click="openComponent" />
                 <el-button type="primary" :icon="isRowExpansion ? 'FolderOpened' : 'Folder'" @click="tableExpandAll">
                     <span v-if="isRowExpansion">全部收起</span>
                     <span v-else>全部展开</span>
                 </el-button>
             </template>
             <template #operate="{ row }">
-                <app-auth-operation :menus="['system_menu_edit', 'system_menu_delete']" :options="{ selectedRow: row }"
-                    linkButton />
+                <app-action-menu :menus="getAuthButtons(['system_menu_edit', 'system_menu_delete'])" :record="row"
+                    @click="openComponent" linkButton />
             </template>
         </app-table>
+        <component ref="componentRef" :is="asyncComponent" v-bind="{ selectedRow: activeMenu.record }"
+            @closed="closeComponent" v-if="asyncComponent" />
     </app-view>
 </template>
 
@@ -20,40 +22,22 @@
 import { ref } from 'vue'
 import { useDataTable } from '@/hooks/datatable'
 import { useMenu } from '@/hooks/menu'
-import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+import { useActionMenu } from '@pc/components/modules/action-menu'
+import AppActionMenu from '@pc/components/modules/action-menu/index.vue'
 import AppTable from '@pc/components/base/table/index.vue'
 
 const { userMenus } = useMenu()
+const { activeMenu, asyncComponent, getAuthButtons, openComponent, closeComponent } = useActionMenu<Model.UserMenu>()
 const { dataList = userMenus } = useDataTable<Model.UserMenu>()
 const tableRef = ref()
 const isRowExpansion = ref(false)
 
 const columns = ref([
-    {
-        prop: 'title',
-        label: '菜单',
-        show: true,
-    },
-    {
-        prop: 'code',
-        label: '代码',
-        show: true,
-    },
-    {
-        prop: 'component',
-        label: '组件',
-        show: true,
-    },
-    {
-        prop: 'icon',
-        label: '图标',
-        show: true,
-    },
-    {
-        prop: 'operate',
-        label: '操作',
-        show: true,
-    }
+    { prop: 'title', label: '菜单', },
+    { prop: 'code', label: '代码', },
+    { prop: 'component', label: '组件', },
+    { prop: 'icon', label: '图标', },
+    { prop: 'operate', label: '操作', }
 ])
 
 // 展开或收起表格

+ 14 - 30
src/packages/pc/views/system/role/index.vue

@@ -2,13 +2,16 @@
     <app-view class="system-role">
         <app-table :data="dataList" v-model:columns="columns">
             <template #header>
-                <app-auth-operation :menus="['system_role_add', 'system_role_delete']" />
+                <app-action-menu :menus="getAuthButtons(['system_role_add', 'system_role_delete'])"
+                    @click="openComponent" />
             </template>
             <template #operate="{ row }">
-                <app-auth-operation :menus="['system_role_auth', 'system_role_edit', 'system_role_delete']"
-                    :options="{ selectedRow: row }" linkButton />
+                <app-action-menu :menus="getAuthButtons(['system_role_auth', 'system_role_edit', 'system_role_delete'])"
+                    :record="row" @click="openComponent" linkButton />
             </template>
         </app-table>
+        <component ref="componentRef" :is="asyncComponent" v-bind="{ selectedRow: activeMenu.record }"
+            @closed="closeComponent" v-if="asyncComponent" />
     </app-view>
 </template>
 
@@ -16,38 +19,19 @@
 import { ref } from 'vue'
 import { useDataTable } from '@/hooks/datatable'
 import { queryAccountRole } from '@/services/api/account'
-import AppAuthOperation from '@pc/components/modules/auth-operation/index.vue'
+import { useActionMenu } from '@pc/components/modules/action-menu'
+import AppActionMenu from '@pc/components/modules/action-menu/index.vue'
 import AppTable from '@pc/components/base/table/index.vue'
 
+const { activeMenu, asyncComponent, getAuthButtons, openComponent, closeComponent } = useActionMenu<Model.UserRole>()
 const { dataList } = useDataTable<Model.UserRole>()
 
 const columns = ref([
-    {
-        prop: 'id',
-        label: '序号',
-        width: 100,
-        show: true,
-    },
-    {
-        prop: 'roleName',
-        label: '角色名称',
-        show: true,
-    },
-    {
-        prop: 'createdAt',
-        label: '创建时间',
-        show: true,
-    },
-    {
-        prop: 'updatedAt',
-        label: '更新时间',
-        show: true,
-    },
-    {
-        prop: 'operate',
-        label: '操作',
-        show: true,
-    }
+    { prop: 'id', label: '序号', width: 100, },
+    { prop: 'roleName', label: '角色名称', },
+    { prop: 'createdAt', label: '创建时间', },
+    { prop: 'updatedAt', label: '更新时间', },
+    { prop: 'operate', label: '操作', }
 ])
 
 queryAccountRole({

+ 0 - 49
src/packages/pc/views/user/components/layout/index.less

@@ -1,49 +0,0 @@
-.sign-layout {
-    display        : flex;
-    justify-content: center;
-    align-items    : center;
-    height         : 100%;
-    background     : url("~@pc/assets/images/loginBackground.jpg") no-repeat center center;
-    background-size: cover;
-
-    &__wrapper {
-        display         : flex;
-        width           : 690px;
-        height          : 450px;
-        border-radius   : 5px;
-        background-color: #fff;
-        box-shadow      : 0 5px 10px 0 rgba(18, 22, 24, .18);
-        overflow        : hidden;
-    }
-
-    .logo {
-        width          : 275px;
-        text-align     : center;
-        background     : url("~@pc/assets/images/logoBackground.jpg") no-repeat center center;
-        background-size: cover;
-
-        &-image {
-            margin-top: 75px;
-        }
-    }
-
-    .login {
-        flex           : 1;
-        display        : flex;
-        flex-direction : column;
-        justify-content: center;
-        padding        : 0 55px;
-
-        &-title {
-            font-size    : 26px;
-            margin-bottom: 20px;
-        }
-
-        &-container {
-            .el-button.submit {
-                width : 100%;
-                height: 40px;
-            }
-        }
-    }
-}

+ 0 - 28
src/packages/pc/views/user/components/layout/index.vue

@@ -1,28 +0,0 @@
-<template>
-  <div class="sign-layout">
-    <div class="sign-layout__wrapper">
-      <div class="logo">
-        <img class="logo-image" src="@pc/assets/logo.png" title="logo" />
-      </div>
-      <div class="login">
-        <h2 class="login-title">{{ title }}</h2>
-        <div class="login-container">
-          <slot></slot>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-defineProps({
-  title: {
-    type: String,
-    required: true,
-  },
-});
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 14 - 0
src/services/api/common/index.ts

@@ -91,4 +91,18 @@ export function queryMyRegisterMoney(params: HttpParams<{ req: Model.MyRegisterM
  */
 export function queryUserLevelInfo(params: HttpParams<{ req: Model.UserLevelInfoReq, rsp: Model.UserLevelInfoRsp }>) {
     return httpRequest('/Ferroalloy/QueryUserLevelInfo', 'get', params);
+}
+
+/**
+ * 查询好友
+ */
+export function queryTHJFriends(params: HttpParams<{ req: Model.THJFriendsReq, rsp: Model.THJFriendsRsp[] }>) {
+    return httpRequest('/Ferroalloy/QueryTHJFriends', 'get', params);
+}
+
+/**
+ * 查询收益
+ */
+export function queryTHJProfits(params: HttpParams<{ req: Model.THJProfitsReq, rsp: Model.THJProfitsRsp[] }>) {
+    return httpRequest('/Ferroalloy/QueryTHJProfits', 'get', params);
 }

+ 1 - 0
src/types/model/account.d.ts

@@ -282,6 +282,7 @@ declare global {
             icon: string; // 菜单图标
             buttonName: string; // 按钮名称
             buttonType: string; // 按钮类型
+            className: string; // 菜单样式
             sort: number; // 排序
             hidden: boolean; // 是否隐藏
             remark: string; // 备注

+ 48 - 0
src/types/model/common.d.ts

@@ -159,6 +159,54 @@ declare global {
             progress: number; // 进度
         }
 
+        /** 查询好友 请求 */
+        interface THJFriendsReq {
+            userid: number; // 用户ID
+            customername?: string; // 客户名称(企业名称),模糊查询
+            mobile?: string; // 手机号码(加密存储),加密串精确查询
+            levelgroupid?: number; // 等级ID
+            page?: number; // 页码
+            pagesize: number?; // 每页条数
+        }
+
+        /** 查询好友 响应 */
+        interface THJFriendsRsp {
+            accoutstatus: string; // 状态
+            createtime: string; // 创建时间
+            customername: string; // 客户名称(企业名称),模糊查询
+            groupname: string; // 分级名称
+            hasauth: number; // 是否已实名认证 - 0:未认证 1:已认证 2:已提交(待审核) 3:已拒绝
+            mobile: string; // 手机号码(加密存储),加密串精确查询
+        }
+
+        /** 查询收益 请求 */
+        interface THJProfitsReq {
+            userid: number; // 用户ID
+            marketid?: number; // 市场ID
+            accountname?: string; // 好友名称,模糊查询
+            goods?: string; // 商品代码或商品名称,模糊查询
+            page?: number; // 页码
+            pagesize: number?; // 每页条数
+        }
+
+        /** 查询收益 响应 */
+        interface THJProfitsRsp {
+            brokerprofitrate: string; // 会员释出比例
+            brokerrate: string; // 会员比例
+            buyorselldisplay: string; // 方向
+            chargeamount: number; // 手续费总额
+            firend: string; // 下单好友
+            levelonevalue: string; // 一级比例
+            leveltwovalue: string; // 二级比例
+            marketname: string; // 市场
+            profitamount: number; // 分润金额
+            profitroletypedisplay: string; // 分润角色
+            tradeid: string; // 成交单号
+            tradeqty: number; // 成交数量
+            tradetimedisplay: string; // 成交时间
+            wrstandardname: string; // 商品
+        }
+
         /** 文件上传请求 */
         interface uploadFileReq {
             /// 文件信息

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff