Merge branch 'master' of https://gitee.com/xingyuv/yudao-ui-admin-vben
						commit
						1f75f376f6
					
				
							
								
								
									
										10
									
								
								README.md
								
								
								
								
							
							
						
						
									
										10
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -13,11 +13,8 @@
 | 
			
		|||
## 开发文档
 | 
			
		||||
[开发文档](./dev.md)
 | 
			
		||||
 | 
			
		||||
### 说明
 | 
			
		||||
- 本项目为ruoyi-vue-pro vue3 antdv 版本ui
 | 
			
		||||
- 基于vben2.9.0版本并升级到最新的依赖,后续将升级antdv4
 | 
			
		||||
- 目前开发中
 | 
			
		||||
</div>
 | 
			
		||||
## 交流群
 | 
			
		||||
<img alt="index.vue" width="400px" src="./docimg/wx.jpg"></img>
 | 
			
		||||
 | 
			
		||||
## 开发进度
 | 
			
		||||
- 系统管理 页面适配 99%
 | 
			
		||||
| 
						 | 
				
			
			@ -94,9 +91,6 @@ pnpm front
 | 
			
		|||
pnpm build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 交流群
 | 
			
		||||
<img alt="index.vue" width="400px" src="./docimg/code.jpg"></img>
 | 
			
		||||
 | 
			
		||||
## Git 贡献提交规范
 | 
			
		||||
 | 
			
		||||
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ import { configVisualizerConfig } from './visualizer'
 | 
			
		|||
import { configThemePlugin } from './theme'
 | 
			
		||||
import { configSvgIconsPlugin } from './svgSprite'
 | 
			
		||||
 | 
			
		||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
 | 
			
		||||
export async function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
 | 
			
		||||
  const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv
 | 
			
		||||
 | 
			
		||||
  const vitePlugins: (PluginOption | PluginOption[])[] = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
 | 
			
		||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
 | 
			
		||||
import path from 'path'
 | 
			
		||||
import { PluginOption } from 'vite'
 | 
			
		||||
 | 
			
		||||
export function configSvgIconsPlugin(isBuild: boolean) {
 | 
			
		||||
  const svgIconsPlugin = createSvgIconsPlugin({
 | 
			
		||||
| 
						 | 
				
			
			@ -13,5 +14,5 @@ export function configSvgIconsPlugin(isBuild: boolean) {
 | 
			
		|||
    // default
 | 
			
		||||
    symbolId: 'icon-[dir]-[name]'
 | 
			
		||||
  })
 | 
			
		||||
  return svgIconsPlugin
 | 
			
		||||
  return svgIconsPlugin as PluginOption
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
 */
 | 
			
		||||
import visualizer from 'rollup-plugin-visualizer'
 | 
			
		||||
import { isReportMode } from '../../utils'
 | 
			
		||||
import { PluginOption } from 'vite'
 | 
			
		||||
 | 
			
		||||
export function configVisualizerConfig() {
 | 
			
		||||
  if (isReportMode()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +12,7 @@ export function configVisualizerConfig() {
 | 
			
		|||
      open: true,
 | 
			
		||||
      gzipSize: true,
 | 
			
		||||
      brotliSize: true
 | 
			
		||||
    }) as Plugin
 | 
			
		||||
    }) as PluginOption
 | 
			
		||||
  }
 | 
			
		||||
  return []
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								docimg/code.jpg
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								docimg/code.jpg
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 144 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 144 KiB  | 
| 
						 | 
				
			
			@ -140,8 +140,10 @@
 | 
			
		|||
    "url": "https://github.com/xingyuv/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://github.com/xingyuv",
 | 
			
		||||
  "packageManager": "pnpm@8.1.0",
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">= 16.0.0"
 | 
			
		||||
    "node": ">= 16.0.0",
 | 
			
		||||
    "pnpm": ">=7.30.0"
 | 
			
		||||
  },
 | 
			
		||||
  "lint-staged": {
 | 
			
		||||
    "*.{js,jsx,ts,tsx}": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4084
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										4084
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
import { defHttp } from '@/utils/http/axios'
 | 
			
		||||
import { UploadFileParams } from '@/types/axios'
 | 
			
		||||
 | 
			
		||||
export interface ProfileDept {
 | 
			
		||||
  id: number
 | 
			
		||||
| 
						 | 
				
			
			@ -87,10 +86,7 @@ export function updateUserPwdApi(oldPassword: string, newPassword: string) {
 | 
			
		|||
 | 
			
		||||
// 用户头像上传
 | 
			
		||||
export function uploadAvatarApi(data) {
 | 
			
		||||
  const params: UploadFileParams = {
 | 
			
		||||
    file: data
 | 
			
		||||
  }
 | 
			
		||||
  return defHttp.uploadFile({ url: Api.uploadAvatarApi }, params)
 | 
			
		||||
  return defHttp.put({ url: Api.uploadAvatarApi, data: { file: data } })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 社交绑定,使用 code 授权码
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,5 +7,6 @@ export default {
 | 
			
		|||
  delete: 'Delete',
 | 
			
		||||
  detail: 'Detail',
 | 
			
		||||
  export: 'Export',
 | 
			
		||||
  import: 'Import'
 | 
			
		||||
  import: 'Import',
 | 
			
		||||
  sync: 'Sync'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,5 +7,6 @@ export default {
 | 
			
		|||
  delete: '删除',
 | 
			
		||||
  detail: '详情',
 | 
			
		||||
  export: '导出',
 | 
			
		||||
  import: '导入'
 | 
			
		||||
  import: '导入',
 | 
			
		||||
  sync: '同步'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,4 +14,54 @@ export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): str
 | 
			
		|||
  return dayjs(date).format(format)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function beginOfDay(date) {
 | 
			
		||||
  return new Date(date.getFullYear(), date.getMonth(), date.getDate())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function endOfDay(date) {
 | 
			
		||||
  return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function betweenDay(date1, date2) {
 | 
			
		||||
  date1 = convertDate(date1)
 | 
			
		||||
  date2 = convertDate(date2)
 | 
			
		||||
  // 计算差值
 | 
			
		||||
  return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function formatDate(date, fmt) {
 | 
			
		||||
  date = convertDate(date)
 | 
			
		||||
  const o = {
 | 
			
		||||
    'M+': date.getMonth() + 1, //月份
 | 
			
		||||
    'd+': date.getDate(), //日
 | 
			
		||||
    'H+': date.getHours(), //小时
 | 
			
		||||
    'm+': date.getMinutes(), //分
 | 
			
		||||
    's+': date.getSeconds(), //秒
 | 
			
		||||
    'q+': Math.floor((date.getMonth() + 3) / 3), //季度
 | 
			
		||||
    S: date.getMilliseconds() //毫秒
 | 
			
		||||
  }
 | 
			
		||||
  if (/(y+)/.test(fmt)) {
 | 
			
		||||
    // 年份
 | 
			
		||||
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
 | 
			
		||||
  }
 | 
			
		||||
  for (const k in o) {
 | 
			
		||||
    if (new RegExp('(' + k + ')').test(fmt)) {
 | 
			
		||||
      fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return fmt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function addTime(date, time) {
 | 
			
		||||
  date = convertDate(date)
 | 
			
		||||
  return new Date(date.getTime() + time)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertDate(date) {
 | 
			
		||||
  if (typeof date === 'string') {
 | 
			
		||||
    return new Date(date)
 | 
			
		||||
  }
 | 
			
		||||
  return date
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const dateUtil = dayjs
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
          <div class="mb-2">头像</div>
 | 
			
		||||
          <CropperAvatar
 | 
			
		||||
            :value="avatar"
 | 
			
		||||
            :uploadApi="uploadAvatarApi as any"
 | 
			
		||||
            btnText="更换头像"
 | 
			
		||||
            :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
 | 
			
		||||
            @change="updateAvatar"
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +57,6 @@ async function updateAvatar({ src, data }) {
 | 
			
		|||
  const userinfo = userStore.getUserInfo
 | 
			
		||||
  userinfo.user.avatar = src
 | 
			
		||||
  userStore.setUserInfo(userinfo)
 | 
			
		||||
  console.log('data', data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleSubmit() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,30 @@ export interface ListItem {
 | 
			
		|||
  color?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tab的list
 | 
			
		||||
export const settingList = [
 | 
			
		||||
  {
 | 
			
		||||
    key: '1',
 | 
			
		||||
    name: '基本设置',
 | 
			
		||||
    component: 'BaseSetting'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '2',
 | 
			
		||||
    name: '安全设置',
 | 
			
		||||
    component: 'SecureSetting'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '3',
 | 
			
		||||
    name: '账号绑定',
 | 
			
		||||
    component: 'AccountBind'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '4',
 | 
			
		||||
    name: '新消息通知',
 | 
			
		||||
    component: 'MsgNotify'
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
// 基础设置 form
 | 
			
		||||
export const baseSetschemas: FormSchema[] = [
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@
 | 
			
		|||
              { icon: IconEnum.DOWNLOAD, label: '生成', auth: 'infra:codegen:download', onClick: handleGenTable.bind(null, record) },
 | 
			
		||||
              {
 | 
			
		||||
                icon: IconEnum.RESET,
 | 
			
		||||
                label: '同步',
 | 
			
		||||
                label: t('action.sync'),
 | 
			
		||||
                auth: 'infra:codegen:update',
 | 
			
		||||
                popConfirm: {
 | 
			
		||||
                  title: '确认要强制同步' + record.tableName + '表结构吗?',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ import { ref, unref } from 'vue'
 | 
			
		|||
import { BasicModal, useModalInner } from '@/components/Modal'
 | 
			
		||||
import { BasicForm, useForm } from '@/components/Form'
 | 
			
		||||
import { formSchema } from './dataSourceConfig.data'
 | 
			
		||||
import { createPost, getPost, updatePost } from '@/api/system/post'
 | 
			
		||||
import { createDataSourceConfig, getDataSourceConfig, updateDataSourceConfig } from '@/api/infra/dataSourceConfig'
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['success', 'register'])
 | 
			
		||||
const isUpdate = ref(true)
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
 | 
			
		|||
  setModalProps({ confirmLoading: false })
 | 
			
		||||
  isUpdate.value = !!data?.isUpdate
 | 
			
		||||
  if (unref(isUpdate)) {
 | 
			
		||||
    const res = await getPost(data.record.id)
 | 
			
		||||
    const res = await getDataSourceConfig(data.record.id)
 | 
			
		||||
    setFieldsValue({ ...res })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -36,9 +36,9 @@ async function handleSubmit() {
 | 
			
		|||
    const values = await validate()
 | 
			
		||||
    setModalProps({ confirmLoading: true })
 | 
			
		||||
    if (unref(isUpdate)) {
 | 
			
		||||
      await updatePost(values)
 | 
			
		||||
      await updateDataSourceConfig(values)
 | 
			
		||||
    } else {
 | 
			
		||||
      await createPost(values)
 | 
			
		||||
      await createDataSourceConfig(values)
 | 
			
		||||
    }
 | 
			
		||||
    closeModal()
 | 
			
		||||
    emit('success')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,8 @@
 | 
			
		|||
      <template #bodyCell="{ column, record }">
 | 
			
		||||
        <template v-if="column.key === 'action'">
 | 
			
		||||
          <TableAction
 | 
			
		||||
            :actions="[
 | 
			
		||||
              { icon: IconEnum.EDIT, label: t('action.edit'), auth: 'mp:account:update', onClick: handleEdit.bind(null, record) },
 | 
			
		||||
            :actions="[{ icon: IconEnum.EDIT, label: t('action.edit'), auth: 'mp:account:update', onClick: handleEdit.bind(null, record) }]"
 | 
			
		||||
            :drop-down-actions="[
 | 
			
		||||
              {
 | 
			
		||||
                icon: IconEnum.RESET,
 | 
			
		||||
                label: '生成二维码',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    <BasicTable @register="registerTable">
 | 
			
		||||
      <template #toolbar>
 | 
			
		||||
        <a-button type="primary" v-auth="['mp:user:sync']" :preIcon="IconEnum.RESET" @click="handleSync">
 | 
			
		||||
          {{ t('action.create') }}
 | 
			
		||||
          {{ t('action.sync') }}
 | 
			
		||||
        </a-button>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #bodyCell="{ column }">
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +49,7 @@ const [registerTable, { getForm, reload }] = useTable({
 | 
			
		|||
/** 同步按钮操作 */
 | 
			
		||||
async function handleSync() {
 | 
			
		||||
  createConfirm({
 | 
			
		||||
    title: '同步粉丝',
 | 
			
		||||
    title: t('action.sync'),
 | 
			
		||||
    iconType: 'warning',
 | 
			
		||||
    content: '是否确认同步粉丝?',
 | 
			
		||||
    async onOk() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
<!-- <template>
 | 
			
		||||
  <Card title="接口分析数据" :loading="loading">
 | 
			
		||||
    <div ref="chartRef" :style="{ width, height }"></div>
 | 
			
		||||
  </Card>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Ref, ref, watch } from 'vue'
 | 
			
		||||
import { Card } from 'ant-design-vue'
 | 
			
		||||
import { useECharts } from '@/hooks/web/useECharts'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  loading: Boolean,
 | 
			
		||||
  newUser: propTypes.array,
 | 
			
		||||
  cancelUser: propTypes.array,
 | 
			
		||||
  width: propTypes.string.def('100%'),
 | 
			
		||||
  height: propTypes.string.def('300px')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<HTMLDivElement | null>(null)
 | 
			
		||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
 | 
			
		||||
 | 
			
		||||
const newUserData = ref<any[]>(props.newUser)
 | 
			
		||||
const cancelUserData = ref<any[]>(props.cancelUser)
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.loading,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (props.loading) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    setOptions({
 | 
			
		||||
      color: ['#67C23A', '#e5323e', '#E6A23C', '#409EFF'],
 | 
			
		||||
      legend: {
 | 
			
		||||
        data: ['被动回复用户消息的次数', '失败次数', '最大耗时', '总耗时']
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {},
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        data: [] // X 轴的日期范围
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {},
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '被动回复用户消息的次数',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          barGap: 0,
 | 
			
		||||
          data: [] // 被动回复用户消息的次数的数据
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: '失败次数',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          data: [] // 失败次数的数据
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: '最大耗时',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          data: [] // 最大耗时的数据
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: '总耗时',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          data: [] // 总耗时的数据
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
</script> -->
 | 
			
		||||
<template><div>开发中</div></template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<!-- <template>
 | 
			
		||||
  <Card title="消息概况数据" :loading="loading">
 | 
			
		||||
    <div ref="chartRef" :style="{ width, height }"></div>
 | 
			
		||||
  </Card>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Ref, ref, watch } from 'vue'
 | 
			
		||||
import { Card } from 'ant-design-vue'
 | 
			
		||||
import { useECharts } from '@/hooks/web/useECharts'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  loading: Boolean,
 | 
			
		||||
  newUser: propTypes.array,
 | 
			
		||||
  cancelUser: propTypes.array,
 | 
			
		||||
  width: propTypes.string.def('100%'),
 | 
			
		||||
  height: propTypes.string.def('300px')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<HTMLDivElement | null>(null)
 | 
			
		||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
 | 
			
		||||
 | 
			
		||||
const newUserData = ref<any[]>(props.newUser)
 | 
			
		||||
const cancelUserData = ref<any[]>(props.cancelUser)
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.loading,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (props.loading) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    setOptions({
 | 
			
		||||
      color: ['#67C23A', '#e5323e'],
 | 
			
		||||
      legend: {
 | 
			
		||||
        data: ['用户发送人数', '用户发送条数']
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {},
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        data: [] // X 轴的日期范围
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        minInterval: 1
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '用户发送人数',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          data: newUserData.value // 用户发送人数的数据
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: '用户发送条数',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          data: cancelUserData.value // 用户发送条数的数据
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
</script> -->
 | 
			
		||||
<template><div>开发中</div></template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
<!-- <template>
 | 
			
		||||
  <Card title="累计用户数据" :loading="loading">
 | 
			
		||||
    <div ref="chartRef" :style="{ width, height }"></div>
 | 
			
		||||
  </Card>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Ref, onMounted, ref, watch } from 'vue'
 | 
			
		||||
import { Card } from 'ant-design-vue'
 | 
			
		||||
import { useECharts } from '@/hooks/web/useECharts'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
import { getUserCumulate } from '@/api/mp/statistics'
 | 
			
		||||
import { beginOfDay, endOfDay, formatToDateTime } from '@/utils/dateUtil'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  loading: Boolean,
 | 
			
		||||
  accountId: propTypes.number,
 | 
			
		||||
  dataTime: {
 | 
			
		||||
    type: Array as PropType<Date[]>,
 | 
			
		||||
    default: () => [
 | 
			
		||||
      beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), // -7 天
 | 
			
		||||
      endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  width: propTypes.string.def('100%'),
 | 
			
		||||
  height: propTypes.string.def('300px')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<HTMLDivElement | null>(null)
 | 
			
		||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
 | 
			
		||||
 | 
			
		||||
const optionsData = ref()
 | 
			
		||||
 | 
			
		||||
async function initData() {
 | 
			
		||||
  const res = await getUserCumulate({
 | 
			
		||||
    accountId: props.accountId,
 | 
			
		||||
    date: [formatToDateTime(props.dataTime[0]), formatToDateTime(props.dataTime[1])]
 | 
			
		||||
  })
 | 
			
		||||
  optionsData.value = res.cumulateUser
 | 
			
		||||
}
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.loading,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (props.loading) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    setOptions({
 | 
			
		||||
      legend: {
 | 
			
		||||
        data: ['累计用户量']
 | 
			
		||||
      },
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
        data: optionsData.value
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        minInterval: 1
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '累计用户量',
 | 
			
		||||
          data: [], // 累计用户量的数据
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          smooth: true
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  await initData()
 | 
			
		||||
})
 | 
			
		||||
</script> -->
 | 
			
		||||
<template><div>开发中</div></template>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
<!-- <template>
 | 
			
		||||
  <Card title="用户增减数据" :loading="loading">
 | 
			
		||||
    <div ref="chartRef" :style="{ width, height }"></div>
 | 
			
		||||
  </Card>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Ref, onMounted, ref, watch } from 'vue'
 | 
			
		||||
import { Card } from 'ant-design-vue'
 | 
			
		||||
import { useECharts } from '@/hooks/web/useECharts'
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
import { getUserSummary } from '@/api/mp/statistics'
 | 
			
		||||
import { addTime, beginOfDay, betweenDay, endOfDay, formatDate, formatToDateTime } from '@/utils/dateUtil'
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  loading: Boolean,
 | 
			
		||||
  accountId: propTypes.number,
 | 
			
		||||
  dataTime: {
 | 
			
		||||
    type: Array as PropType<Date[]>,
 | 
			
		||||
    default: () => [
 | 
			
		||||
      beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), // -7 天
 | 
			
		||||
      endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  width: propTypes.string.def('100%'),
 | 
			
		||||
  height: propTypes.string.def('300px')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const chartRef = ref<HTMLDivElement | null>(null)
 | 
			
		||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
 | 
			
		||||
 | 
			
		||||
const optionsData = ref()
 | 
			
		||||
 | 
			
		||||
const xAxisDate = ref()
 | 
			
		||||
 | 
			
		||||
async function initData() {
 | 
			
		||||
  const res = await getUserSummary({
 | 
			
		||||
    accountId: props.accountId,
 | 
			
		||||
    date: [formatToDateTime(props.dataTime[0]), formatToDateTime(props.dataTime[1])]
 | 
			
		||||
  })
 | 
			
		||||
  optionsData.value = res
 | 
			
		||||
  xAxisDate.value = []
 | 
			
		||||
  const days = betweenDay(props.dataTime[0], props.dataTime[1]) // 相差天数
 | 
			
		||||
  for (let i = 0; i <= days; i++) {
 | 
			
		||||
    xAxisDate.value.push(formatDate(addTime(props.dataTime[0], 3600 * 1000 * 24 * i), 'yyyy-MM-dd'))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.loading,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (props.loading) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    setOptions({
 | 
			
		||||
      color: ['#67C23A', '#e5323e'],
 | 
			
		||||
      legend: {
 | 
			
		||||
        data: ['新增用户', '取消关注的用户']
 | 
			
		||||
      },
 | 
			
		||||
      tooltip: {},
 | 
			
		||||
      xAxis: {
 | 
			
		||||
        data: xAxisDate.value // X 轴的日期范围
 | 
			
		||||
      },
 | 
			
		||||
      yAxis: {
 | 
			
		||||
        minInterval: 1
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '新增用户',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          barGap: 0,
 | 
			
		||||
          data: optionsData.value.newUserData // 新增用户的数据
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: '取消关注的用户',
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          data: optionsData.value.cancelUserData // 取消关注的用户的数据
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  await initData()
 | 
			
		||||
})
 | 
			
		||||
</script> -->
 | 
			
		||||
<template><div>开发中</div></template>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,20 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>开发中</div>
 | 
			
		||||
<!-- <template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <UserSummaryChart class="md:w-1/2 w-full" :loading="loading" :accountId="accountId" />
 | 
			
		||||
    <UserCumulateChart class="md:w-1/2 w-full" :loading="loading" :accountId="accountId" />
 | 
			
		||||
    <UpstreamMessageChart class="md:w-1/2 w-full" :loading="loading" :accountId="accountId" />
 | 
			
		||||
    <InterfaceSummaryChart class="md:w-1/2 w-full" :loading="loading" :accountId="accountId" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup name="Statistics">
 | 
			
		||||
import { ref } from 'vue'
 | 
			
		||||
import UserSummaryChart from './components/UserSummaryChart.vue'
 | 
			
		||||
import UserCumulateChart from './components/UserCumulateChart.vue'
 | 
			
		||||
import UpstreamMessageChart from './components/UpstreamMessageChart.vue'
 | 
			
		||||
import InterfaceSummaryChart from './components/InterfaceSummaryChart.vue'
 | 
			
		||||
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
 | 
			
		||||
const accountId = ref(1)
 | 
			
		||||
</script> -->
 | 
			
		||||
<template><div>开发中</div></template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <BasicModal v-bind="$attrs" @register="registerModal" :title="isUpdate ? '编辑' : '新增'" @ok="handleSubmit">
 | 
			
		||||
    <BasicForm @register="registerForm" />
 | 
			
		||||
  </BasicModal>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup name="PostModal">
 | 
			
		||||
import { ref, unref } from 'vue'
 | 
			
		||||
import { BasicModal, useModalInner } from '@/components/Modal'
 | 
			
		||||
import { BasicForm, useForm } from '@/components/Form'
 | 
			
		||||
import { formSchema } from './tag.data'
 | 
			
		||||
import { createTag, updateTag, getTag } from '@/api/mp/tag'
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['success', 'register'])
 | 
			
		||||
const isUpdate = ref(true)
 | 
			
		||||
 | 
			
		||||
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
 | 
			
		||||
  labelWidth: 120,
 | 
			
		||||
  baseColProps: { span: 24 },
 | 
			
		||||
  schemas: formSchema,
 | 
			
		||||
  showActionButtonGroup: false,
 | 
			
		||||
  actionColOptions: { span: 23 }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
 | 
			
		||||
  resetFields()
 | 
			
		||||
  setModalProps({ confirmLoading: false })
 | 
			
		||||
  isUpdate.value = !!data?.isUpdate
 | 
			
		||||
  if (unref(isUpdate)) {
 | 
			
		||||
    const res = await getTag(data.record.id)
 | 
			
		||||
    setFieldsValue({ ...res })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
async function handleSubmit() {
 | 
			
		||||
  try {
 | 
			
		||||
    const values = await validate()
 | 
			
		||||
    setModalProps({ confirmLoading: true })
 | 
			
		||||
    if (unref(isUpdate)) {
 | 
			
		||||
      await updateTag(values)
 | 
			
		||||
    } else {
 | 
			
		||||
      await createTag(values)
 | 
			
		||||
    }
 | 
			
		||||
    closeModal()
 | 
			
		||||
    emit('success')
 | 
			
		||||
  } finally {
 | 
			
		||||
    setModalProps({ confirmLoading: false })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,90 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>开发中</div>
 | 
			
		||||
  <div>
 | 
			
		||||
    <BasicTable @register="registerTable">
 | 
			
		||||
      <template #toolbar>
 | 
			
		||||
        <a-button type="primary" v-auth="['mp:tag:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
 | 
			
		||||
          {{ t('action.create') }}
 | 
			
		||||
        </a-button>
 | 
			
		||||
        <a-button type="warning" v-auth="['mp:tag:sync']" :preIcon="IconEnum.RESET" @click="handleSync">
 | 
			
		||||
          {{ t('action.sync') }}
 | 
			
		||||
        </a-button>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #bodyCell="{ column, record }">
 | 
			
		||||
        <template v-if="column.key === 'action'">
 | 
			
		||||
          <TableAction
 | 
			
		||||
            :actions="[
 | 
			
		||||
              { icon: IconEnum.EDIT, label: t('action.edit'), auth: 'mp:tag:update', onClick: handleEdit.bind(null, record) },
 | 
			
		||||
              {
 | 
			
		||||
                icon: IconEnum.DELETE,
 | 
			
		||||
                color: 'error',
 | 
			
		||||
                label: t('action.delete'),
 | 
			
		||||
                auth: 'mp:tag:delete',
 | 
			
		||||
                popConfirm: {
 | 
			
		||||
                  title: t('common.delMessage'),
 | 
			
		||||
                  placement: 'left',
 | 
			
		||||
                  confirm: handleDelete.bind(null, record)
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            ]"
 | 
			
		||||
          />
 | 
			
		||||
        </template>
 | 
			
		||||
      </template>
 | 
			
		||||
    </BasicTable>
 | 
			
		||||
    <TagModal @register="registerModal" @success="reload()" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup name="Tag">
 | 
			
		||||
import { useI18n } from '@/hooks/web/useI18n'
 | 
			
		||||
import { useMessage } from '@/hooks/web/useMessage'
 | 
			
		||||
import { useModal } from '@/components/Modal'
 | 
			
		||||
import TagModal from './TagModal.vue'
 | 
			
		||||
import { IconEnum } from '@/enums/appEnum'
 | 
			
		||||
import { BasicTable, useTable, TableAction } from '@/components/Table'
 | 
			
		||||
import { deleteTag, getTagPage, syncTag } from '@/api/mp/tag'
 | 
			
		||||
import { columns, searchFormSchema } from './tag.data'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const { createConfirm, createMessage } = useMessage()
 | 
			
		||||
const [registerModal, { openModal }] = useModal()
 | 
			
		||||
 | 
			
		||||
const [registerTable, { getForm, reload }] = useTable({
 | 
			
		||||
  title: '标签列表',
 | 
			
		||||
  api: getTagPage,
 | 
			
		||||
  columns,
 | 
			
		||||
  formConfig: { labelWidth: 120, schemas: searchFormSchema },
 | 
			
		||||
  useSearchForm: true,
 | 
			
		||||
  showTableSetting: true,
 | 
			
		||||
  actionColumn: {
 | 
			
		||||
    width: 140,
 | 
			
		||||
    title: t('common.action'),
 | 
			
		||||
    dataIndex: 'action',
 | 
			
		||||
    fixed: 'right'
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function handleCreate() {
 | 
			
		||||
  openModal(true, { isUpdate: false })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleEdit(record: Recordable) {
 | 
			
		||||
  openModal(true, { record, isUpdate: true })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleSync() {
 | 
			
		||||
  createConfirm({
 | 
			
		||||
    title: t('action.sync'),
 | 
			
		||||
    iconType: 'warning',
 | 
			
		||||
    content: t('common.exportMessage'),
 | 
			
		||||
    async onOk() {
 | 
			
		||||
      await syncTag(getForm().getFieldsValue().accountId)
 | 
			
		||||
      createMessage.success(t('common.exportSuccessText'))
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleDelete(record: Recordable) {
 | 
			
		||||
  await deleteTag(record.id)
 | 
			
		||||
  createMessage.success(t('common.delSuccessText'))
 | 
			
		||||
  reload()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
import { getSimpleAccounts } from '@/api/mp/account'
 | 
			
		||||
import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 | 
			
		||||
 | 
			
		||||
export const columns: BasicColumn[] = [
 | 
			
		||||
  {
 | 
			
		||||
    title: '编号',
 | 
			
		||||
    dataIndex: 'id',
 | 
			
		||||
    width: 100
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '标签名称',
 | 
			
		||||
    dataIndex: 'name',
 | 
			
		||||
    width: 180
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '粉丝数',
 | 
			
		||||
    dataIndex: 'count',
 | 
			
		||||
    width: 100
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '创建时间',
 | 
			
		||||
    dataIndex: 'createTime',
 | 
			
		||||
    width: 180,
 | 
			
		||||
    customRender: ({ text }) => {
 | 
			
		||||
      return useRender.renderDate(text)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const searchFormSchema: FormSchema[] = [
 | 
			
		||||
  {
 | 
			
		||||
    label: '公众号',
 | 
			
		||||
    field: 'accountId',
 | 
			
		||||
    component: 'ApiSelect',
 | 
			
		||||
    componentProps: {
 | 
			
		||||
      api: () => getSimpleAccounts(),
 | 
			
		||||
      labelField: 'name',
 | 
			
		||||
      valueField: 'id'
 | 
			
		||||
    },
 | 
			
		||||
    colProps: { span: 8 }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '标签名称',
 | 
			
		||||
    field: 'name',
 | 
			
		||||
    component: 'Input',
 | 
			
		||||
    colProps: { span: 8 }
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const formSchema: FormSchema[] = [
 | 
			
		||||
  {
 | 
			
		||||
    label: '编号',
 | 
			
		||||
    field: 'id',
 | 
			
		||||
    show: false,
 | 
			
		||||
    component: 'Input'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: '标签名称',
 | 
			
		||||
    field: 'name',
 | 
			
		||||
    required: true,
 | 
			
		||||
    component: 'Input'
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ const __APP_INFO__ = {
 | 
			
		|||
  lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ({ command, mode }: ConfigEnv): UserConfig => {
 | 
			
		||||
export default async ({ command, mode }: ConfigEnv): Promise<UserConfig> => {
 | 
			
		||||
  const root = process.cwd()
 | 
			
		||||
 | 
			
		||||
  const env = loadEnv(mode, root)
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +92,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    // The vite plugin used by the project. The quantity is large, so it is separately extracted and managed
 | 
			
		||||
    plugins: createVitePlugins(viteEnv, isBuild),
 | 
			
		||||
    plugins: await createVitePlugins(viteEnv, isBuild),
 | 
			
		||||
 | 
			
		||||
    optimizeDeps: { include, exclude }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue