Merge branch 'dev' of gitee.com:yudaocode/yudao-ui-admin-vue3 into dev

Signed-off-by: dhb52 <dhb52@126.com>
pull/110/head
dhb52 2023-04-09 05:16:23 +00:00 committed by Gitee
commit 75750c1bd3
100 changed files with 1669 additions and 1105 deletions

3
.env
View File

@ -15,3 +15,6 @@ VITE_APP_CAPTCHA_ENABLE=true
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
# 百度统计
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc

View File

@ -16,7 +16,7 @@ VITE_API_BASEPATH=/dev-api
VITE_API_URL=/admin-api
# 打包路径
VITE_BASE_PATH=/dist-dev/
VITE_BASE_PATH=/
# 是否删除debugger
VITE_DROP_DEBUGGER=false

View File

@ -6,39 +6,3 @@ import request from '@/config/axios'
export const getCache = () => {
return request.get({ url: '/infra/redis/get-monitor-info' })
}
// 获取模块
export const getKeyDefineList = () => {
return request.get({ url: '/infra/redis/get-key-define-list' })
}
/**
* redis key
*/
export const getKeyList = (keyTemplate: string) => {
return request.get({
url: '/infra/redis/get-key-list',
params: {
keyTemplate
}
})
}
// 获取缓存内容
export const getKeyValue = (key: string) => {
return request.get({ url: '/infra/redis/get-key-value?key=' + key })
}
// 根据键名删除缓存
export const deleteKey = (key: string) => {
return request.delete({ url: '/infra/redis/delete-key?key=' + key })
}
export const deleteKeys = (keyTemplate: string) => {
return request.delete({
url: '/infra/redis/delete-keys?',
params: {
keyTemplate
}
})
}

View File

@ -174,12 +174,3 @@ export interface RedisCommandStatsVO {
calls: number
usec: number
}
export interface RedisKeyInfo {
keyTemplate: string
keyType: string
valueType: string
timeoutType: number
timeout: number
memo: string
}

View File

@ -2,15 +2,11 @@ import request from '@/config/axios'
import { getRefreshToken } from '@/utils/auth'
import type { UserLoginVO } from './types'
export interface CodeImgResult {
captchaOnOff: boolean
img: string
uuid: string
}
export interface SmsCodeVO {
mobile: string
scene: number
}
export interface SmsLoginVO {
mobile: string
code: string

View File

@ -0,0 +1,41 @@
import request from '@/config/axios'
// 获得授权信息
export const getAuthorize = (clientId: string) => {
return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
}
// 发起授权
export const authorize = (
responseType: string,
clientId: string,
redirectUri: string,
state: string,
autoApprove: boolean,
checkedScopes: string[],
uncheckedScopes: string[]
) => {
// 构建 scopes
const scopes = {}
for (const scope of checkedScopes) {
scopes[scope] = true
}
for (const scope of uncheckedScopes) {
scopes[scope] = false
}
// 发起请求
return request.post({
url: '/system/oauth2/authorize',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
},
params: {
response_type: responseType,
client_id: clientId,
redirect_uri: redirectUri,
state: state,
auto_approve: autoApprove,
scope: JSON.stringify(scopes)
}
})
}

View File

@ -26,17 +26,3 @@ export type UserVO = {
loginIp: string
loginDate: string
}
export type UserInfoVO = {
permissions: []
roles: []
user: {
avatar: string
id: number
nickname: string
}
}
export type TentantNameVO = {
name: string
}

View File

@ -0,0 +1,56 @@
import request from '@/config/axios'
/**
*
*/
export interface BrandVO {
/**
*
*/
id?: number
/**
*
*/
name: string
/**
*
*/
picUrl: string
/**
*
*/
sort?: number
/**
*
*/
description?: string
/**
*
*/
status: number
}
// 创建商品品牌
export const createBrand = (data: BrandVO) => {
return request.post({ url: '/product/brand/create', data })
}
// 更新商品品牌
export const updateBrand = (data: BrandVO) => {
return request.put({ url: '/product/brand/update', data })
}
// 删除商品品牌
export const deleteBrand = (id: number) => {
return request.delete({ url: `/product/brand/delete?id=${id}` })
}
// 获得商品品牌
export const getBrand = (id: number) => {
return request.get({ url: `/product/brand/get?id=${id}` })
}
// 获得商品品牌列表
export const getBrandParam = (params: PageParam) => {
return request.get({ url: '/product/brand/page', params })
}

View File

@ -23,7 +23,7 @@
</el-table>
<el-dialog
v-model="modelVisible"
v-model="dialogVisible"
:title="modelConfig.title"
:close-on-click-modal="false"
width="400px"
@ -39,7 +39,7 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="modelVisible = false"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="addNewObject"> </el-button>
</template>
</el-dialog>
@ -49,7 +49,7 @@
const message = useMessage()
const signalList = ref<any[]>([])
const messageList = ref<any[]>([])
const modelVisible = ref(false)
const dialogVisible = ref(false)
const modelType = ref('')
const modelObjectForm = ref<any>({})
const rootElements = ref()
@ -85,7 +85,7 @@ const initDataList = () => {
const openModel = (type) => {
modelType.value = type
modelObjectForm.value = {}
modelVisible.value = true
dialogVisible.value = true
}
const addNewObject = () => {
if (modelType.value === 'message') {
@ -101,7 +101,7 @@ const addNewObject = () => {
const signalRef = bpmnInstances().moddle.create('bpmn:Signal', modelObjectForm.value)
rootElements.value.push(signalRef)
}
modelVisible.value = false
dialogVisible.value = false
initDataList()
}

View File

@ -1,8 +1,8 @@
import axios, {
AxiosError,
AxiosInstance,
AxiosRequestHeaders,
AxiosResponse,
AxiosError,
InternalAxiosRequestConfig
} from 'axios'
@ -230,7 +230,8 @@ const handleAuthorized = () => {
wsCache.clear()
removeToken()
isRelogin.show = false
window.location.href = import.meta.env.VITE_BASE_PATH
// 干掉token后再走一次路由让它过router.beforeEach的校验
window.location.href = window.location.href
})
}
return Promise.reject(t('sys.api.timeoutMessage'))

View File

@ -352,6 +352,7 @@ export default {
login: {
backSignIn: '返回',
signInFormTitle: '登录',
ssoFormTitle: '三方授权',
mobileSignInFormTitle: '手机登录',
qrSignInFormTitle: '二维码登录',
signUpFormTitle: '注册',

View File

@ -52,6 +52,8 @@ import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
import hljs from 'highlight.js' //导入代码高亮文件
import 'highlight.js/styles/github.css' //导入代码高亮样式 新版
import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger'
// 本地开发模式 全局引入 element-plus 样式,加快第一次进入速度

View File

@ -0,0 +1,23 @@
import router from '@/router'
// 用于 router push
window._hmt = window._hmt || []
// HM_ID
const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE
;(function () {
// 有值的时候,才开启
if (!HM_ID) {
return
}
const hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID
const s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
router.afterEach(function (to) {
if (!HM_ID) {
return
}
_hmt.push(['_trackPageview', to.fullPath])
})

View File

@ -1,11 +1,11 @@
import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import remainingRouter from './modules/remaining'
// 创建路由实例
const router = createRouter({
history: createWebHashHistory(), // createWebHashHistory URL带#createWebHistory URL不带#
history: createWebHistory(), // createWebHashHistory URL带#createWebHistory URL不带#
strict: true,
routes: remainingRouter as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 })

View File

@ -116,7 +116,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: 'type/data/:dictType',
component: () => import('@/views/system/dict/data/index.vue'),
name: 'data',
name: 'SystemDictData',
meta: {
title: '字典数据',
noCache: true,
@ -140,7 +140,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: 'edit',
component: () => import('@/views/infra/codegen/EditTable.vue'),
name: 'EditTable',
name: 'InfraCodegenEditTable',
meta: {
noCache: true,
hidden: true,
@ -163,7 +163,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: 'job-log',
component: () => import('@/views/infra/job/logger/index.vue'),
name: 'JobLog',
name: 'InfraJobLog',
meta: {
noCache: true,
hidden: true,
@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
noTagsView: true
}
},
{
path: '/sso',
component: () => import('@/views/Login/Login.vue'),
name: 'SSOLogin',
meta: {
hidden: true,
title: t('router.login'),
noTagsView: true
}
},
{
path: '/403',
component: () => import('@/views/Error/403.vue'),
@ -226,7 +236,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: '/manager/form/edit',
component: () => import('@/views/bpm/form/editor/index.vue'),
name: 'bpmFormEditor',
name: 'BpmFormEditor',
meta: {
noCache: true,
hidden: true,
@ -238,7 +248,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: '/manager/model/edit',
component: () => import('@/views/bpm/model/editor/index.vue'),
name: 'modelEditor',
name: 'BpmModelEditor',
meta: {
noCache: true,
hidden: true,
@ -250,7 +260,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: '/manager/definition',
component: () => import('@/views/bpm/definition/index.vue'),
name: 'BpmProcessDefinitionList',
name: 'BpmProcessDefinition',
meta: {
noCache: true,
hidden: true,
@ -262,7 +272,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: '/manager/task-assign-rule',
component: () => import('@/views/bpm/taskAssignRule/index.vue'),
name: 'BpmTaskAssignRuleList',
name: 'BpmTaskAssignRule',
meta: {
noCache: true,
hidden: true,
@ -305,18 +315,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
title: '发起 OA 请假',
activeMenu: 'bpm/oa/leave/create'
}
},
{
path: '/bpm/oa/leave/detail',
component: () => import('@/views/bpm/oa/leave/detail.vue'),
name: 'OALeaveDetail',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '查看 OA 请假',
activeMenu: 'bpm/oa/leave/detail'
}
}
]
},
@ -331,7 +329,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: 'value/:propertyId(\\d+)',
component: () => import('@/views/mall/product/property/value/index.vue'),
name: 'PropertyValue',
name: 'ProductPropertyValue',
meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
}
]

View File

@ -25,13 +25,12 @@ declare module '@vue/runtime-core' {
Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@ -71,19 +70,14 @@ declare module '@vue/runtime-core' {
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTableV2: typeof import('element-plus/es')['ElTableV2']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
Error: typeof import('./../components/Error/src/Error.vue')['default']
FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']

View File

@ -112,7 +112,6 @@ export enum DICT_TYPE {
// ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
INFRA_REDIS_TIMEOUT_TYPE = 'infra_redis_timeout_type',
INFRA_JOB_STATUS = 'infra_job_status',
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',

View File

@ -9,19 +9,19 @@
>
<!-- 左上角的 logo + 系统标题 -->
<div class="flex items-center relative text-white">
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<!-- 左边的背景图 + 欢迎语 -->
<div class="flex justify-center items-center h-[calc(100%-60px)]">
<TransitionGroup
appear
tag="div"
enter-active-class="animate__animated animate__bounceInLeft"
tag="div"
>
<img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
<div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
<div class="mt-5 font-normal text-white text-14px" key="3">
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
<div key="3" class="mt-5 font-normal text-white text-14px">
{{ t('login.message') }}
</div>
</TransitionGroup>
@ -31,7 +31,7 @@
<!-- 右上角的主题语言选择 -->
<div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
<div class="flex items-center @2xl:hidden @xl:hidden">
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
</div>
<div class="flex justify-end items-center space-x-10px">
@ -52,20 +52,23 @@
<QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
<!-- 注册 -->
<RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
<!-- 三方登录 -->
<SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
</div>
</Transition>
</div>
</div>
</div>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { underlineToHump } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign'
import { useAppStore } from '@/store/modules/app'
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components'
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
const { t } = useI18n()
const appStore = useAppStore()

View File

@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon'
import * as authUtil from '@/utils/auth'
import { usePermissionStore } from '@/store/modules/permission'
import * as LoginApi from '@/api/login'
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
const { t } = useI18n()
const message = useMessage()
@ -240,7 +240,12 @@ const handleLogin = async (params) => {
if (!redirect.value) {
redirect.value = '/'
}
push({ path: redirect.value || permissionStore.addRouters[0].path })
// SSO
if (redirect.value.indexOf('sso') !== -1) {
window.location.href = window.location.href.replace('/login?redirect=', '')
} else {
push({ path: redirect.value || permissionStore.addRouters[0].path })
}
} catch {
loginLoading.value = false
} finally {
@ -291,6 +296,7 @@ onMounted(() => {
color: var(--el-color-primary) !important;
}
}
.login-code {
width: 100%;
height: 38px;

View File

@ -16,7 +16,8 @@ const getFormTitle = computed(() => {
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle')
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
[LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')
}
return titleObj[unref(getLoginState)]
})

View File

@ -0,0 +1,186 @@
<template>
<div v-show="ssoVisible" class="form-cont">
<!-- 应用名 -->
<LoginFormTitle style="width: 100%" />
<el-tabs class="form" style="float: none" value="uname">
<el-tab-pane :label="client.name" name="uname" />
</el-tabs>
<div>
<el-form :model="formData" class="login-form">
<!-- 授权范围的选择 -->
此第三方应用请求获得以下权限
<el-form-item prop="scopes">
<el-checkbox-group v-model="formData.scopes">
<el-checkbox
v-for="scope in queryParams.scopes"
:key="scope"
:label="scope"
style="display: block; margin-bottom: -10px"
>
{{ formatScope(scope) }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 下方的登录按钮 -->
<el-form-item class="w-1/1">
<el-button
:loading="formLoading"
class="w-6/10"
type="primary"
@click.prevent="handleAuthorize(true)"
>
<span v-if="!formLoading"></span>
<span v-else> ...</span>
</el-button>
<el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script lang="ts" name="SSOLogin" setup>
import LoginFormTitle from './LoginFormTitle.vue'
import * as OAuth2Api from '@/api/login/oauth2'
import { LoginStateEnum, useLoginState } from './useLogin'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
const route = useRoute() //
const { currentRoute } = useRouter() //
const { getLoginState, setLoginState } = useLoginState()
const client = ref({
//
name: '',
logo: ''
})
const queryParams = reactive({
// URL client_idscope
responseType: '',
clientId: '',
redirectUri: '',
state: '',
scopes: [] // query
})
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // SSO
const formData = reactive({
scopes: [] // scope
})
const formLoading = ref(false) //
/** 初始化授权信息 */
const init = async () => {
//
if (typeof route.query.client_id === 'undefined') return
//
// client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
// client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
queryParams.responseType = route.query.response_type as string
queryParams.clientId = route.query.client_id as string
queryParams.redirectUri = route.query.redirect_uri as string
queryParams.state = route.query.state as string
if (route.query.scope) {
queryParams.scopes = (route.query.scope as string).split(' ')
}
// scope
if (queryParams.scopes.length > 0) {
const data = await doAuthorize(true, queryParams.scopes, [])
if (data) {
location.href = data
return
}
}
//
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
client.value = data.client
// scope
let scopes
// 1.1 params.scope scopes
if (queryParams.scopes.length > 0) {
scopes = []
for (const scope of data.scopes) {
if (queryParams.scopes.indexOf(scope.key) >= 0) {
scopes.push(scope)
}
}
// 1.2 params.scope 使 scopes
} else {
scopes = data.scopes
for (const scope of scopes) {
queryParams.scopes.push(scope.key)
}
}
// checkedScopes
for (const scope of scopes) {
if (scope.value) {
formData.scopes.push(scope.key)
}
}
}
/** 处理授权的提交 */
const handleAuthorize = async (approved) => {
// checkedScopes + uncheckedScopes
let checkedScopes
let uncheckedScopes
if (approved) {
//
checkedScopes = formData.scopes
uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
} else {
//
checkedScopes = []
uncheckedScopes = queryParams.scopes
}
//
formLoading.value = true
try {
const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
if (!data) {
return
}
location.href = data
} finally {
formLoading.value = false
}
}
/** 调用授权 API 接口 */
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
return OAuth2Api.authorize(
queryParams.responseType,
queryParams.clientId,
queryParams.redirectUri,
queryParams.state,
autoApprove,
checkedScopes,
uncheckedScopes
)
}
/** 格式化 scope 文本 */
const formatScope = (scope) => {
// scope 便
// demo "system_oauth2_scope" scope
switch (scope) {
case 'user.read':
return '访问你的个人信息'
case 'user.write':
return '修改你的个人信息'
default:
return scope
}
}
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
if (route.name === 'SSOLogin') {
setLoginState(LoginStateEnum.SSO)
init()
}
},
{ immediate: true }
)
</script>

View File

@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue'
import LoginFormTitle from './LoginFormTitle.vue'
import RegisterForm from './RegisterForm.vue'
import QrCodeForm from './QrCodeForm.vue'
import SSOLoginVue from './SSOLogin.vue'
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm }
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }

View File

@ -5,7 +5,8 @@ export enum LoginStateEnum {
REGISTER,
RESET_PASSWORD,
MOBILE,
QR_CODE
QR_CODE,
SSO
}
const currentState = ref(LoginStateEnum.LOGIN)

View File

@ -93,7 +93,7 @@
</Dialog>
</template>
<script setup lang="ts" name="Form">
<script setup lang="ts" name="BpmProcessDefinition">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as DefinitionApi from '@/api/bpm/definition'

View File

@ -83,12 +83,11 @@
</Dialog>
</template>
<script setup lang="ts" name="Form">
<script setup lang="ts" name="BpmForm">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
@ -130,7 +129,7 @@ const resetQuery = () => {
/** 添加/修改操作 */
const openForm = (id?: number) => {
push({
name: 'bpmFormEditor',
name: 'BpmFormEditor',
query: {
id
}

View File

@ -111,7 +111,7 @@
<UserGroupForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="UserGroup">
<script setup lang="ts" name="BpmUserGroup">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as UserGroupApi from '@/api/bpm/userGroup'

View File

@ -24,7 +24,7 @@
</ContentWrap>
</template>
<script setup lang="ts">
<script setup lang="ts" name="BpmModelEditor">
//
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
//

View File

@ -224,7 +224,7 @@
</Dialog>
</template>
<script setup lang="ts" name="Form">
<script setup lang="ts" name="BpmModel">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
@ -319,7 +319,7 @@ const handleChangeState = async (row) => {
/** 设计流程 */
const handleDesign = (row) => {
push({
name: 'modelEditor',
name: 'BpmModelEditor',
query: {
modelId: row.id
}
@ -352,7 +352,7 @@ const handleAssignRule = (row) => {
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
push({
name: 'BpmProcessDefinitionList',
name: 'BpmProcessDefinition',
query: {
key: row.key
}

View File

@ -46,7 +46,7 @@
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
</ContentWrap>
</template>
<script setup lang="ts">
<script setup lang="ts" name="BpmProcessInstanceCreate">
import { DICT_TYPE } from '@/utils/dict'
import * as DefinitionApi from '@/api/bpm/definition'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'

View File

@ -96,7 +96,7 @@
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
</ContentWrap>
</template>
<script setup lang="ts">
<script setup lang="ts" name="BpmProcessInstanceDetail">
import { useUserStore } from '@/store/modules/user'
import { setConfAndFields2 } from '@/utils/formCreate'
import type { ApiAttrs } from '@form-create/element-ui/types/config'

View File

@ -1,64 +1,211 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="所属流程" prop="processDefinitionId">
<el-input
v-model="queryParams.processDefinitionId"
placeholder="请输入流程定义的编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程分类" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="结果" prop="result">
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="提交时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
title="发起流程"
plain
v-hasPermi="['bpm:process-instance:query']"
@click="handleCreate"
/>
</template>
<!-- 流程分类 -->
<template #category_default="{ row }">
<DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
</template>
<!-- 当前审批任务 -->
<template #tasks_default="{ row }">
<el-button v-for="task in row.tasks" :key="task.id" link>
<span>{{ task.name }}</span>
>
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
</el-button>
</template>
<!-- 操作 -->
<template #actionbtns_default="{ row }">
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['bpm:process-instance:cancel']"
@click="handleDetail(row)"
/>
<XTextButton
preIcon="ep:delete"
title="取消"
v-if="row.result === 1"
v-hasPermi="['bpm:process-instance:query']"
@click="handleCancel(row)"
/>
</template>
</XTable>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程编号" align="center" prop="id" width="300px" />
<el-table-column label="流程名称" align="center" prop="name" />
<el-table-column label="流程分类" align="center" prop="category">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="当前审批任务" align="center" prop="tasks">
<template #default="scope">
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
<span>{{ task.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="状态" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="结果" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
label="提交时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
v-hasPermi="['bpm:process-instance:cancel']"
@click="handleDetail(scope.row)"
>
详情
</el-button>
<el-button
link
type="primary"
v-if="scope.row.result === 1"
v-hasPermi="['bpm:process-instance:query']"
@click="handleCancel(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
// import
<script setup lang="ts" name="BpmProcessInstance">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
// import
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { allSchemas } from './process.data'
const router = useRouter() //
const message = useMessage() //
const { t } = useI18n() //
// ========== ==========
const [registerTable, { reload }] = useXTable({
allSchemas: allSchemas,
getListApi: ProcessInstanceApi.getMyProcessInstancePage
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
processDefinitionId: undefined,
category: undefined,
status: undefined,
result: undefined,
createTime: []
})
const queryFormRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getMyProcessInstancePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 发起流程操作 **/
const handleCreate = () => {
@ -67,7 +214,7 @@ const handleCreate = () => {
})
}
//
/** 查看详情 */
const handleDetail = (row) => {
router.push({
name: 'BpmProcessInstanceDetail',
@ -78,16 +225,23 @@ const handleDetail = (row) => {
}
/** 取消按钮操作 */
const handleCancel = (row) => {
ElMessageBox.prompt('请输入取消原因', '取消流程', {
const handleCancel = async (row) => {
//
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, //
inputErrorMessage: '取消原因不能为空'
}).then(async ({ value }) => {
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
message.success('取消成功')
reload()
})
//
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
message.success('取消成功')
//
await getList()
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,94 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: null,
primaryTitle: '编号',
action: true,
actionWidth: '200px',
columns: [
{
title: '编号',
field: 'id',
table: {
width: 320
}
},
{
title: '流程名',
field: 'name',
isSearch: true
},
{
title: '所属流程',
field: 'processDefinitionId',
isSearch: true,
isTable: false
},
{
title: '流程分类',
field: 'category',
dictType: DICT_TYPE.BPM_MODEL_CATEGORY,
dictClass: 'number',
isSearch: true,
table: {
slots: {
default: 'category_default'
}
}
},
{
title: '当前审批任务',
field: 'tasks',
table: {
width: 140,
slots: {
default: 'tasks_default'
}
}
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '结果',
field: 'result',
dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
dictClass: 'number',
isSearch: true
},
{
title: '提交时间',
field: 'createTime',
formatter: 'formatDate',
table: {
width: 180
},
isForm: false,
isSearch: true,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '结束时间',
field: 'endTime',
formatter: 'formatDate',
table: {
width: 180
},
isForm: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -74,7 +74,7 @@
<!-- 表单弹窗详情 -->
<TaskDetail ref="detailRef" @success="getList" />
</template>
<script setup lang="tsx">
<script setup lang="tsx" name="BpmTodoTask">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'

View File

@ -1,32 +1,117 @@
<template>
<ContentWrap>
<XTable @register="registerTable">
<template #suspensionState_default="{ row }">
<el-tag type="success" v-if="row.suspensionState === 1"></el-tag>
<el-tag type="warning" v-if="row.suspensionState === 2"></el-tag>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作: 审批进度 -->
<XTextButton preIcon="ep:edit-pen" title="审批进度" @click="handleAudit(row)" />
</template>
</XTable>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="任务名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入任务名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="任务编号" align="center" prop="id" width="300px" />
<el-table-column label="任务名称" align="center" prop="name" />
<el-table-column label="所属流程" align="center" prop="processInstance.name" />
<el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="任务状态" prop="suspensionState">
<template #default="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1"></el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2"></el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="handleAudit(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
// import
import { allSchemas } from './todo.data'
<script setup lang="tsx" name="BpmDoneTask">
import { dateFormatter } from '@/utils/formatTime'
const { push } = useRouter() //
import * as TaskApi from '@/api/bpm/task'
const { push } = useRouter() //
const [registerTable] = useXTable({
allSchemas: allSchemas,
topActionSlots: false,
getListApi: TaskApi.getTodoTaskPage
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
createTime: []
})
const queryFormRef = ref() //
//
/** 查询任务列表 */
const getList = async () => {
loading.value = true
try {
const data = await TaskApi.getTodoTaskPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 处理审批按钮 */
const handleAudit = (row) => {
push({
name: 'BpmProcessInstanceDetail',
@ -35,4 +120,9 @@ const handleAudit = (row) => {
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,58 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// crudSchemas
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: null,
action: true,
searchSpan: 8,
columns: [
{
title: '任务编号',
field: 'id',
table: {
width: 320
}
},
{
title: '任务名称',
field: 'name',
isSearch: true
},
{
title: '所属流程',
field: 'processInstance.name'
},
{
title: '流程发起人',
field: 'processInstance.startUserNickname'
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
table: {
width: 180
},
isSearch: true,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '任务状态',
field: 'suspensionState',
table: {
slots: {
default: 'suspensionState_default'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -32,7 +32,7 @@
<!-- 添加/修改弹窗 -->
<TaskAssignRuleForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="TaskAssignRule">
<script setup lang="ts" name="BpmTaskAssignRule">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import * as RoleApi from '@/api/system/role'

View File

@ -139,7 +139,7 @@
<!-- 表单弹窗详情 -->
<ApiAccessLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="ApiAccessLog">
<script setup lang="ts" name="InfraApiAccessLog">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import download from '@/utils/download'
import { formatDate } from '@/utils/formatTime'

View File

@ -158,14 +158,13 @@
<ApiErrorLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="ApiErrorLog">
<script setup lang="ts" name="InfraApiErrorLog">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
const message = useMessage() //
const loading = ref(true) //

View File

@ -31,7 +31,7 @@
</div>
</Dialog>
</template>
<script setup lang="ts" name="Build">
<script setup lang="ts" name="InfraBuild">
import formCreate from '@form-create/element-ui'
import { useClipboard } from '@vueuse/core'
const { t } = useI18n() //

View File

@ -142,7 +142,7 @@
<!-- 弹窗预览代码 -->
<PreviewCode ref="previewRef" />
</template>
<script setup lang="ts" name="Codegen">
<script setup lang="ts" name="InfraCodegen">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as CodegenApi from '@/api/infra/codegen'

View File

@ -137,7 +137,7 @@
<!-- 表单弹窗添加/修改 -->
<ConfigForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
<script setup lang="ts" name="InfraConfig">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -57,7 +57,7 @@
<!-- 表单弹窗添加/修改 -->
<DataSourceConfigForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="DataSourceConfig">
<script setup lang="ts" name="InfraDataSourceConfig">
import { dateFormatter } from '@/utils/formatTime'
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
import DataSourceConfigForm from './DataSourceConfigForm.vue'

View File

@ -2,46 +2,38 @@
<doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" />
<ContentWrap title="数据库文档">
<!-- 操作工具栏 -->
<div class="mb-10px">
<XButton
type="primary"
preIcon="ep:download"
:title="t('action.export') + ' HTML'"
@click="handleExport('HTML')"
/>
<XButton
type="primary"
preIcon="ep:download"
:title="t('action.export') + ' Word'"
@click="handleExport('Word')"
/>
<XButton
type="primary"
preIcon="ep:download"
:title="t('action.export') + ' Markdown'"
@click="handleExport('Markdown')"
/>
<el-button type="primary" plain @click="handleExport('HTML')">
<Icon icon="ep:download" /> 导出 HTML
</el-button>
<el-button type="primary" plain @click="handleExport('Word')">
<Icon icon="ep:download" /> 导出 Word
</el-button>
<el-button type="primary" plain @click="handleExport('Markdown')">
<Icon icon="ep:download" /> 导出 Markdown
</el-button>
</div>
<IFrame v-if="!loding" v-loading="loding" :src="src" />
<IFrame v-if="!loading" v-loading="loading" :src="src" />
</ContentWrap>
</template>
<script setup lang="ts" name="DbDoc">
<script setup lang="ts" name="InfraDBDoc">
import download from '@/utils/download'
import * as DbDocApi from '@/api/infra/dbDoc'
const { t } = useI18n() //
const src = ref('')
const loding = ref(true)
const loading = ref(true) //
const src = ref('') // HTML
/** 页面加载 */
const init = async () => {
const res = await DbDocApi.exportHtml()
let blob = new Blob([res], { type: 'text/html' })
let blobUrl = window.URL.createObjectURL(blob)
src.value = blobUrl
loding.value = false
try {
const data = await DbDocApi.exportHtml()
const blob = new Blob([data], { type: 'text/html' })
src.value = window.URL.createObjectURL(blob)
} finally {
loading.value = false
}
}
/** 处理导出 */
const handleExport = async (type: string) => {
if (type === 'HTML') {
@ -57,6 +49,8 @@ const handleExport = async (type: string) => {
download.markdown(res, '数据库文档.md')
}
}
/** 初始化 */
onMounted(async () => {
await init()
})

View File

@ -3,10 +3,24 @@
<doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
<ContentWrap>
<IFrame :src="src" />
<IFrame v-if="!loading" :src="url" />
</ContentWrap>
</template>
<script setup lang="ts" name="Druid">
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/druid/index.html')
<script setup lang="ts" name="InfraDruid">
import * as ConfigApi from '@/api/infra/config'
const loading = ref(true) //
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
/** 初始化 */
onMounted(async () => {
try {
const data = await ConfigApi.getConfigKey('url.druid')
if (data && data.length > 0) {
url.value = data
}
} finally {
loading.value = false
}
})
</script>

View File

@ -2,17 +2,19 @@
<Dialog title="上传文件" v-model="dialogVisible">
<el-upload
ref="uploadRef"
:limit="1"
accept=".jpg, .png, .gif"
:auto-upload="false"
drag
:headers="headers"
:action="url"
:data="data"
:disabled="formLoading"
:headers="uploadHeaders"
v-model:file-list="fileList"
drag
accept=".jpg, .png, .gif"
:limit="1"
:on-success="submitFormSuccess"
:on-exceed="handleExceed"
:on-error="submitFormError"
:on-change="handleFileChange"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
:disabled="formLoading"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div>
@ -29,44 +31,47 @@
</Dialog>
</template>
<script setup lang="ts">
import { Dialog } from '@/components/Dialog'
import { getAccessToken } from '@/utils/auth'
import { getAccessToken, getTenantId } from '@/utils/auth'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formLoading = ref(false) //
const url = import.meta.env.VITE_UPLOAD_URL
const headers = { Authorization: 'Bearer ' + getAccessToken() }
const uploadHeaders = ref() // Header
const fileList = ref([]) //
const data = ref({ path: '' })
const uploadRef = ref()
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
resetForm()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
/** 处理上传的文件发生变化 */
const handleFileChange = (file) => {
data.value.path = file.name
}
/** 处理文件上传中 */
const handleFileUploadProgress = () => {
formLoading.value = true //
}
/** 发起文件上传 */
/** 提交表单 */
const submitFileForm = () => {
if (fileList.value.length == 0) {
message.error('请上传文件')
return
}
//
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
}
unref(uploadRef)?.submit()
}
/** 文件上传成功处理 */
const handleFileSuccess = () => {
const emit = defineEmits(['success']) // success
const submitFormSuccess = () => {
//
dialogVisible.value = false
formLoading.value = false
@ -75,4 +80,22 @@ const handleFileSuccess = () => {
message.success(t('common.createSuccess'))
emit('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error('上传失败,请您重新上传!')
formLoading.value = false
}
/** 重置表单 */
const resetForm = () => {
//
formLoading.value = false
uploadRef.value?.clearFiles()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
</script>

View File

@ -1,9 +1,14 @@
<template>
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/"/>
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<!-- 搜索 -->
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="文件路径" prop="path">
<el-input
v-model="queryParams.path"
@ -33,7 +38,7 @@
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openForm">
<el-button type="primary" plain @click="openForm">
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
</el-button>
</el-form-item>
@ -86,11 +91,11 @@
<!-- 表单弹窗添加/修改 -->
<FileForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
<script setup lang="ts" name="InfraFile">
import { fileSizeFormatter } from '@/utils'
import { dateFormatter } from '@/utils/formatTime'
import * as FileApi from '@/api/infra/file'
import FileUploadForm from './FileForm.vue'
import FileForm from './FileForm.vue'
const message = useMessage() //
const { t } = useI18n() //

View File

@ -3,17 +3,29 @@
<!-- 搜索 -->
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="配置名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入配置名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="存储器" prop="storage">
<el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
<el-select
v-model="queryParams.storage"
placeholder="请选择存储器"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="dict.value"
@ -30,6 +42,7 @@
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
@ -113,7 +126,7 @@
<!-- 表单弹窗添加/修改 -->
<FileConfigForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
<script setup lang="ts" name="InfraFileConfig">
import * as FileConfigApi from '@/api/infra/fileConfig'
import FileConfigForm from './FileConfigForm.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'

View File

@ -147,7 +147,7 @@
<!-- 表单弹窗查看 -->
<JobDetail ref="detailRef" />
</template>
<script setup lang="ts" name="Job">
<script setup lang="ts" name="InfraJob">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { checkPermi } from '@/utils/permission'
import JobForm from './JobForm.vue'

View File

@ -121,7 +121,7 @@
<!-- 表单弹窗查看 -->
<JobLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="JobLog">
<script setup lang="ts" name="InfraJobLog">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -4,6 +4,7 @@
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
<el-row>
<!-- 基本信息 -->
<el-col :span="24" class="card-box" shadow="hover">
<el-card>
<el-descriptions title="基本信息" :column="6" border>
@ -47,106 +48,33 @@
</el-descriptions>
</el-card>
</el-col>
<!-- 命令统计 -->
<el-col :span="12" class="mt-3">
<el-card :gutter="12" shadow="hover">
<div ref="commandStatsRef" class="h-88"></div>
</el-card>
</el-col>
<!-- 内存使用量统计 -->
<el-col :span="12" class="mt-3">
<el-card class="ml-3" :gutter="12" shadow="hover">
<div ref="usedmemory" class="h-88"></div>
</el-card>
</el-col>
</el-row>
<el-row class="mt-3">
<el-col :span="24" class="card-box" shadow="hover">
<el-card>
<el-table
v-loading="keyListLoad"
:data="keyList"
row-key="id"
@row-click="openKeyTemplate"
>
<el-table-column prop="keyTemplate" label="Key 模板" width="200" />
<el-table-column prop="keyType" label="Key 类型" width="100" />
<el-table-column prop="valueType" label="Value 类型" />
<el-table-column prop="timeoutType" label="超时时间" width="200">
<template #default="{ row }">
<DictTag :type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE" :value="row?.timeoutType" />
<span v-if="row?.timeout > 0">({{ row?.timeout / 1000 }} )</span>
</template>
</el-table-column>
<el-table-column prop="memo" label="备注" />
</el-table>
</el-card>
</el-col>
</el-row>
</el-scrollbar>
<XModal v-model="dialogVisible" :title="keyTemplate + ' 模板'">
<el-row>
<el-col :span="14" class="mt-3">
<el-card shadow="always">
<template #header>
<div class="card-header">
<span>键名列表</span>
</div>
</template>
<el-table :data="cacheKeys" style="width: 100%" @row-click="handleKeyValue">
<el-table-column label="缓存键名" align="center" :show-overflow-tooltip="true">
<template #default="{ row }">
{{ row }}
</template>
</el-table-column>
<el-table-column label="操作" align="right" width="60">
<template #default="{ row }">
<XTextButton preIcon="ep:delete" @click="handleDeleteKey(row)" />
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="10" class="mt-3">
<el-card shadow="always">
<template #header>
<div class="card-header">
<span>缓存内容</span>
<XTextButton
preIcon="ep:refresh"
title="清理全部"
@click="handleDeleteKeys(keyTemplate)"
class="float-right p-1"
/>
</div>
</template>
<el-descriptions :column="1">
<el-descriptions-item label="缓存键名:">{{ cacheForm.key }}</el-descriptions-item>
<el-descriptions-item label="缓存内容:">{{ cacheForm.value }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
</el-row>
</XModal>
</template>
<script setup lang="ts" name="Redis">
<script setup lang="ts" name="InfraRedis">
import * as echarts from 'echarts'
import { DICT_TYPE } from '@/utils/dict'
import * as RedisApi from '@/api/infra/redis'
import { RedisKeyInfo, RedisMonitorInfoVO } from '@/api/infra/redis/types'
const { t } = useI18n() //
const message = useMessage() //
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
const cache = ref<RedisMonitorInfoVO>()
const keyListLoad = ref(true)
const keyList = ref<RedisKeyInfo[]>([])
//
const readRedisInfo = async () => {
const data = await RedisApi.getCache()
cache.value = data
loadEchartOptions(data.commandStats)
const redisKeysInfo = await RedisApi.getKeyDefineList()
keyList.value = redisKeysInfo
keyListLoad.value = false //
}
//
const commandStatsRef = ref<HTMLElement>()
@ -241,40 +169,8 @@ const loadEchartOptions = (stats) => {
]
})
}
const dialogVisible = ref(false)
const keyTemplate = ref('')
const cacheKeys = ref()
const cacheForm = ref<{
key: string
value: string
}>({
key: '',
value: ''
})
const openKeyTemplate = async (row: RedisKeyInfo) => {
keyTemplate.value = row.keyTemplate
cacheKeys.value = await RedisApi.getKeyList(row.keyTemplate)
dialogVisible.value = true
}
const handleDeleteKey = async (row) => {
RedisApi.deleteKey(row)
message.success(t('common.delSuccess'))
}
const handleDeleteKeys = async (row) => {
RedisApi.deleteKeys(row)
message.success(t('common.delSuccess'))
}
const handleKeyValue = async (row) => {
const res = await RedisApi.getKeyValue(row)
cacheForm.value = res
}
onBeforeMount(() => {
readRedisInfo()
})
</script>
<style scoped>
.redis {
height: 600px;
max-height: 860px;
}
</style>

View File

@ -1,10 +1,25 @@
<template>
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
<ContentWrap>
<IFrame :src="src" />
<IFrame v-if="!loading" v-loading="loading" :src="src" />
</ContentWrap>
</template>
<script setup lang="ts" name="AdminServer">
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/admin/applications')
<script setup lang="ts" name="InfraAdminServer">
import * as ConfigApi from '@/api/infra/config'
const loading = ref(true) //
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
/** 初始化 */
onMounted(async () => {
try {
const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
if (data && data.length > 0) {
src.value = data
}
} finally {
loading.value = false
}
})
</script>

View File

@ -1,9 +1,25 @@
<template>
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
<ContentWrap>
<IFrame :src="src" />
<IFrame v-if="!loading" v-loading="loading" :src="src" />
</ContentWrap>
</template>
<script setup lang="ts" name="Skywalking">
<script setup lang="ts" name="InfraSkyWalking">
import * as ConfigApi from '@/api/infra/config'
const loading = ref(true) //
const src = ref('http://skywalking.shop.iocoder.cn')
/** 初始化 */
onMounted(async () => {
try {
const data = await ConfigApi.getConfigKey('url.skywalking')
if (data && data.length > 0) {
src.value = data
}
} finally {
loading.value = false
}
})
</script>

View File

@ -5,8 +5,22 @@
<IFrame :src="src" />
</ContentWrap>
</template>
<script setup lang="ts" name="Swagger">
const BASE_URL = import.meta.env.VITE_BASE_URL
// const src = ref(BASE_URL + '/doc.html')
const src = ref(BASE_URL + '/swagger-ui')
<script setup lang="ts" name="InfraSwagger">
import * as ConfigApi from '@/api/infra/config'
const loading = ref(true) //
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
/** 初始化 */
onMounted(async () => {
try {
const data = await ConfigApi.getConfigKey('url.swagger')
if (data && data.length > 0) {
src.value = data
}
} finally {
loading.value = false
}
})
</script>

View File

@ -0,0 +1,120 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="品牌名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入品牌名称" />
</el-form-item>
<el-form-item label="品牌图片" prop="picUrl">
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" />
</el-form-item>
<el-form-item label="品牌排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="品牌状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="品牌描述">
<el-input v-model="formData.description" type="textarea" placeholder="请输入品牌描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts" name="ProductBrandForm">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as ProductBrandApi from '@/api/mall/product/brand'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
picUrl: '',
status: CommonStatusEnum.ENABLE,
description: ''
})
const formRules = reactive({
name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
picUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '品牌排序不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProductBrandApi.getBrand(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as ProductBrandApi.BrandVO
if (formType.value === 'create') {
await ProductBrandApi.createBrand(data)
message.success(t('common.createSuccess'))
} else {
await ProductBrandApi.updateBrand(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
picUrl: '',
status: CommonStatusEnum.ENABLE,
description: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,177 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="品牌名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入品牌名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['product:brand:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
<el-table-column label="品牌名称" prop="name" sortable />
<el-table-column label="品牌图片" align="center" prop="picUrl">
<template #default="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="品牌图片" class="h-100px" />
</template>
</el-table-column>
<el-table-column label="品牌排序" align="center" prop="sort" />
<el-table-column label="开启状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['product:brand:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['product:brand:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<BrandForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="ProductBrand">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as ProductBrandApi from '@/api/mall/product/brand'
import BrandForm from './BrandForm.vue'
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref<any[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductBrandApi.getBrandParam(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await ProductBrandApi.deleteBrand(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -50,7 +50,7 @@
</template>
</Dialog>
</template>
<script setup lang="ts">
<script setup lang="ts" name="ProductCategory">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import { handleTree } from '@/utils/tree'

View File

@ -92,7 +92,7 @@
<!-- 表单弹窗添加/修改 -->
<PropertyForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
<script setup lang="ts" name="ProductProperty">
import { dateFormatter } from '@/utils/formatTime'
import * as PropertyApi from '@/api/mall/product/property'
import PropertyForm from './PropertyForm.vue'

View File

@ -88,7 +88,7 @@
<!-- 表单弹窗添加/修改 -->
<ValueForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
<script setup lang="ts" name="ProductPropertyValue">
import { dateFormatter } from '@/utils/formatTime'
import * as PropertyApi from '@/api/mall/product/property'
import ValueForm from './ValueForm.vue'

View File

@ -13,15 +13,14 @@
<img class="material-img" :src="item.url" />
<p class="item-name">{{ item.name }}</p>
<el-row class="ope-row">
<el-button type="success" @click="selectMaterialFun(item)"
>选择 <Icon icon="ep:circle-check" />
<el-button type="success" @click="selectMaterialFun(item)">
选择 <Icon icon="ep:circle-check" />
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -39,18 +38,16 @@
<WxVoicePlayer :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
label="上传时间"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
>选择<Icon icon="ep:plus" />
</el-button>
@ -58,8 +55,7 @@
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -79,11 +75,13 @@
<WxVideoPlayer :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="上传时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="操作"
align="center"
@ -98,8 +96,7 @@
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -121,8 +118,7 @@
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -139,7 +135,7 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
import { getMaterialPage } from '@/api/mp/material'
import { getFreePublishPage } from '@/api/mp/freePublish'
import { getDraftPage } from '@/api/mp/draft'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import { dateFormatter } from '@/utils/formatTime'
import { defineComponent, PropType } from 'vue'
export default defineComponent({
@ -244,7 +240,6 @@ export default defineComponent({
getMaterialPageFun,
getPage,
formatDate,
newsTypeRef,
queryParams,
objDataRef,
list,
@ -254,7 +249,6 @@ export default defineComponent({
}
})
</script>
<style lang="scss" scoped>
/*瀑布流样式*/
.waterfall {

View File

@ -139,7 +139,7 @@ import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxLocation from '@/views/mp/components/wx-location/main.vue'
import WxMusic from '@/views/mp/components/wx-music/main.vue'
import { getUser } from '@/api/mp/mpuser'
import { getUser } from '@/api/mp/user'
import { defineComponent } from 'vue'
const message = useMessage() //

View File

@ -12,10 +12,7 @@
<!-- 类型 1文本 -->
<el-tab-pane name="text">
<template #label>
<el-row align="middle">
<icon icon="ep:document" />
文本
</el-row>
<el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
</template>
<el-input
type="textarea"
@ -28,10 +25,7 @@
<!-- 类型 2图片 -->
<el-tab-pane name="image">
<template #label>
<el-row align="middle">
<icon icon="ep:picture" class="mr-5px" />
图片
</el-row>
<el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
</template>
<!-- 情况一已经选择好素材或者上传好图片 -->
<div class="select-item" v-if="objDataRef.url">
@ -39,7 +33,7 @@
<p class="item-name" v-if="objDataRef.name">{{ objDataRef.name }}</p>
<el-row class="ope-row" justify="center">
<el-button type="danger" circle @click="deleteObj">
<icon icon="ep:delete" />
<Icon icon="ep:delete" />
</el-button>
</el-row>
</div>
@ -48,8 +42,7 @@
<!-- 选择素材 -->
<el-col :span="12" class="col-select">
<el-button type="success" @click="openMaterial">
素材库选择
<icon icon="ep:circle-check" />
素材库选择 <Icon icon="ep:circle-check" />
</el-button>
<el-dialog title="选择图片" v-model="dialogImageVisible" width="90%" append-to-body>
<WxMaterialSelect :obj-data="objDataRef" @select-material="selectMaterial" />
@ -70,10 +63,8 @@
<el-button type="primary">上传图片</el-button>
<template #tip>
<span>
<div class="el-upload__tip"
>支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div
></span
>
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div>
</span>
</template>
</el-upload>
</el-col>
@ -82,12 +73,8 @@
<!-- 类型 3语音 -->
<el-tab-pane name="voice">
<template #label>
<el-row align="middle">
<icon icon="ep:phone" />
语音
</el-row>
<el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
</template>
<div class="select-item2" v-if="objDataRef.url">
<p class="item-name">{{ objDataRef.name }}</p>
<div class="item-infos">
@ -121,8 +108,8 @@
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip"
>格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s
<div class="el-upload__tip">
格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s
</div>
</template>
</el-upload>
@ -132,10 +119,7 @@
<!-- 类型 4视频 -->
<el-tab-pane name="video">
<template #label>
<el-row align="middle">
<icon icon="ep:share" />
视频
</el-row>
<el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
</template>
<el-row>
<el-input
@ -158,8 +142,7 @@
<!-- 选择素材 -->
<el-col :span="12">
<el-button type="success" @click="openMaterial">
素材库选择
<icon icon="ep:circle-check" />
素材库选择 <Icon icon="ep:circle-check" />
</el-button>
<el-dialog title="选择视频" v-model="dialogVideoVisible" width="90%" append-to-body>
<WxMaterialSelect :objData="objDataRef" @select-material="selectMaterial" />
@ -177,10 +160,7 @@
:before-upload="beforeVideoUpload"
:on-success="handleUploadSuccess"
>
<el-button type="primary"
>新建视频
<icon icon="ep:upload" />
</el-button>
<el-button type="primary">新建视频 <Icon icon="ep:upload" /></el-button>
</el-upload>
</el-col>
</el-row>
@ -190,17 +170,14 @@
<!-- 类型 5图文 -->
<el-tab-pane name="news">
<template #label>
<el-row align="middle">
<icon icon="ep:reading" />
图文
</el-row>
<el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
</template>
<el-row>
<div class="select-item" v-if="objDataRef.articles?.length > 0">
<WxNews :articles="objDataRef.articles" />
<el-col class="ope-row">
<el-button type="danger" circle @click="deleteObj">
<icon icon="ep:delete" />
<Icon icon="ep:delete" />
</el-button>
</el-col>
</div>
@ -208,8 +185,8 @@
<el-col :span="24" v-if="!objDataRef.content">
<el-row style="text-align: center" align="middle">
<el-col :span="24">
<el-button type="success" @click="openMaterial"
>{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
<el-button type="success" @click="openMaterial">
{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
<icon icon="ep:circle-check" />
</el-button>
</el-col>
@ -227,10 +204,7 @@
<!-- 类型 6音乐 -->
<el-tab-pane name="music">
<template #label>
<el-row align="middle">
<icon icon="ep:service" />
音乐
</el-row>
<el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
</template>
<el-row align="middle" justify="center">
<el-col :span="6">
@ -295,7 +269,6 @@
</el-tab-pane>
</el-tabs>
</template>
<script lang="ts" name="WxReplySelect">
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'

View File

@ -1,40 +1,37 @@
<template>
<div class="app-container">
<doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
<doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px">
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option
v-for="item in accountList"
:key="parseInt(item.id)"
:key="item.id"
:label="item.name"
:value="parseInt(item.id)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
<Icon icon="ep:plus" />新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="small"
@click="handleAdd"
v-hasPermi="['mp:draft:create']"
><Icon icon="ep:plus" />新增
</el-button>
</el-col>
</el-row>
<!-- 列表 -->
<!-- 列表 -->
<ContentWrap>
<div class="waterfall" v-loading="loading">
<template v-for="item in list" :key="item.articleId">
<div class="waterfall-item" v-if="item.content && item.content.newsItem">
@ -46,35 +43,40 @@
circle
@click="handlePublish(item)"
v-hasPermi="['mp:free-publish:submit']"
><Icon icon="fa:upload"
/></el-button>
>
<Icon icon="fa:upload" />
</el-button>
<el-button
type="primary"
circle
@click="handleUpdate(item)"
v-hasPermi="['mp:draft:update']"
><Icon icon="ep:edit"
/></el-button>
>
<Icon icon="ep:edit" />
</el-button>
<el-button
type="danger"
circle
@click="handleDelete(item)"
v-hasPermi="['mp:draft:delete']"
><Icon icon="ep:delete"
/></el-button>
>
<Icon icon="ep:delete" />
</el-button>
</el-row>
</div>
</template>
</div>
<!-- 分页记录 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- TODO @Dhb52迁移成独立路由 -->
<div class="app-container">
<!-- 添加或修改草稿对话框 -->
<Teleport to="body">
<el-dialog
@ -245,49 +247,39 @@
</el-row>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogNewsVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
<el-button @click="dialogNewsVisible = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</template>
</el-dialog>
</Teleport>
</div>
</template>
<script setup name="MpDraft">
import { ref, onMounted, reactive, nextTick } from 'vue'
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { getAccessToken } from '@/utils/auth'
import { createDraft, deleteDraft, getDraftPage, updateDraft } from '@/api/mp/draft'
import { getSimpleAccountList } from '@/api/mp/account'
import { submitFreePublish } from '@/api/mp/freePublish'
import * as MpAccountApi from '@/api/mp/account'
import * as MpDraftApi from '@/api/mp/draft'
import * as MpFreePublishApi from '@/api/mp/freePublish'
const message = useMessage() //
// API
// import drafts from './mock'
const BASE_URL = import.meta.env.VITE_BASE_URL
const message = useMessage()
const materialSelectRef = ref()
const queryFormRef = ref()
//
const loading = ref(false)
//
//
const total = ref(0)
//
const list = ref([])
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: undefined
})
const queryFormRef = ref() //
const accountList = ref([]) //
// ========== ==========
const materialSelectRef = ref()
const BASE_URL = import.meta.env.VITE_BASE_URL
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') //
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) //
const fileList = ref([])
@ -305,11 +297,10 @@ const dialogImageVisible = ref(false)
const operateMaterial = ref('add')
const articlesMediaId = ref('')
const hackResetEditor = ref(false)
//
const accountList = ref([])
/** 初始化 **/
onMounted(async () => {
accountList.value = await getSimpleAccountList()
accountList.value = await MpAccountApi.getSimpleAccountList()
//
if (accountList.value.length > 0) {
// @ts-ignore
@ -335,7 +326,7 @@ const getList = async () => {
loading.value = true
try {
const drafts = await getDraftPage(queryParams)
const drafts = await MpDraftApi.getDraftPage(queryParams)
drafts.list.forEach((item) => {
const newsItem = item.content.newsItem
// thumbUrl picUrl wx-news
@ -393,9 +384,10 @@ const handleUpdate = (item) => {
/** 提交按钮 */
const submitForm = () => {
// TODO @Dhb52: await
addMaterialLoading.value = true
if (operateMaterial.value === 'add') {
createDraft(queryParams.accountId, articlesAdd.value)
MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
.then(() => {
message.notifySuccess('新增成功')
dialogNewsVisible.value = false
@ -405,7 +397,7 @@ const submitForm = () => {
addMaterialLoading.value = false
})
} else {
updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
.then(() => {
message.notifySuccess('更新成功')
dialogNewsVisible.value = false
@ -559,24 +551,24 @@ const handlePublish = async (item) => {
'你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。'
try {
await message.confirm(content)
await submitFreePublish(accountId, mediaId)
getList()
await MpFreePublishApi.submitFreePublish(accountId, mediaId)
message.notifySuccess('发布成功')
await getList()
} catch {}
}
/** 删除按钮操作 */
const handleDelete = async (item) => {
const accountId = queryParams.accountId
const mediaId = item.mediaId
try {
await message.confirm('此操作将永久删除该草稿, 是否继续?')
await deleteDraft(accountId, mediaId)
getList()
await MpDraftApi.deleteDraft(accountId, mediaId)
message.notifySuccess('删除成功')
await getList()
} catch {}
}
</script>
<style lang="scss" scoped>
.pagination {
float: right;

View File

@ -59,7 +59,7 @@
</ContentWrap>
</template>
<script setup lang="ts" name="freePublish">
<script setup lang="ts" name="MpFreePublish">
import * as FreePublishApi from '@/api/mp/freePublish'
import * as MpAccountApi from '@/api/mp/account'
import WxNews from '@/views/mp/components/wx-news/main.vue'

View File

@ -1,32 +1,32 @@
<template>
<div class="app-container">
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
<!-- 搜索工作栏 -->
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
<el-option
v-for="item in accounts"
:key="parseInt(item.id)"
v-for="item in accountList"
:key="item.id"
:label="item.name"
:value="parseInt(item.id)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-tabs v-model="type" @tab-change="handleTabChange">
<!-- tab 1图片 -->
<el-tab-pane name="image">
@ -44,11 +44,11 @@
:before-upload="beforeImageUpload"
:on-success="handleUploadSuccess"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-button type="primary" plain>点击上传</el-button>
<template #tip>
<span class="el-upload__tip" style="margin-left: 5px"
>支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</span
>
<span class="el-upload__tip" style="margin-left: 5px">
支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M
</span>
</template>
</el-upload>
</div>
@ -64,14 +64,14 @@
circle
@click="handleDelete(item)"
v-hasPermi="['mp:material:delete']"
><Icon icon="ep:delete"
/></el-button>
>
<Icon icon="ep:delete" />
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -95,11 +95,11 @@
:on-success="handleUploadSuccess"
:before-upload="beforeVoiceUpload"
>
<el-button size="small" type="primary">点击上传</el-button>
<el-button type="primary" plain>点击上传</el-button>
<template #tip>
<span class="el-upload__tip" style="margin-left: 5px"
>格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</span
>
<span class="el-upload__tip" style="margin-left: 5px">
格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s
</span>
</template>
</el-upload>
</div>
@ -118,24 +118,23 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button type="primary" link size="small" plain @click="handleDownload(scope.row)"
><Icon icon="ep:download" />下载</el-button
>
<el-button type="primary" link plain @click="handleDownload(scope.row)">
<Icon icon="ep:download" />下载
</el-button>
<el-button
type="primary"
link
size="small"
plain
@click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']"
><Icon icon="ep:delete" />删除</el-button
>
<Icon icon="ep:delete" />删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -149,7 +148,7 @@
<span><Icon icon="ep:video-play" /> 视频</span>
</template>
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
<el-button size="small" type="primary" @click="handleAddVideo"></el-button>
<el-button type="primary" plain @click="handleAddVideo"></el-button>
</div>
<!-- 新建视频的弹窗 -->
<el-dialog
@ -220,14 +219,9 @@
<span>{{ formatDate(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" link size="small" plain @click="handleDownload(scope.row)"
<el-button type="primary" link plain @click="handleDownload(scope.row)"
><Icon icon="ep:download" />下载</el-button
>
<el-button
@ -237,14 +231,14 @@
plain
@click="handleDelete(scope.row)"
v-hasPermi="['mp:material:delete']"
><Icon icon="ep:delete" />删除</el-button
>
<Icon icon="ep:delete" />删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@ -252,11 +246,9 @@
/>
</el-tab-pane>
</el-tabs>
</div>
</ContentWrap>
</template>
<script setup>
import { ref } from 'vue'
<script setup name="MpMaterial">
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
import { getSimpleAccountList } from '@/api/mp/account'
@ -275,8 +267,6 @@ const uploadVideoRef = ref()
const type = ref('image')
//
const loading = ref(false)
//
const showSearch = ref(true)
//
const total = ref(0)
//
@ -308,14 +298,14 @@ const uploadRules = reactive({
})
//
const accounts = ref([])
const accountList = ref([])
onMounted(() => {
getSimpleAccountList().then((data) => {
accounts.value = data
accountList.value = data
//
if (accounts.value.length > 0) {
setAccountId(accounts.value[0].id)
if (accountList.value.length > 0) {
setAccountId(accountList.value[0].id)
}
//
getList()
@ -365,8 +355,8 @@ const handleQuery = () => {
const resetQuery = () => {
queryFormRef.value?.resetFields()
//
if (accounts.value.length > 0) {
setAccountId(accounts.value[0].id)
if (accountList.value.length > 0) {
setAccountId(accountList.value[0].id)
}
handleQuery()
}

View File

@ -1,25 +1,32 @@
<template>
<div class="app-container">
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
<!-- 搜索工作栏 -->
<el-form ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="accountId" placeholder="请选择公众号">
<el-select v-model="accountId" placeholder="请选择公众号" class="!w-240px">
<el-option
v-for="item in accountList"
:key="parseInt(item.id)"
:key="item.id"
:label="item.name"
:value="parseInt(item.id)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" @click="resetQuery"></el-button>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<div class="public-account-management clearfix" v-loading="loading">
<!--左边配置菜单-->
<div class="left">
@ -63,7 +70,6 @@
<el-button
class="save_btn"
type="success"
size="small"
@click="handleSave"
v-hasPermi="['mp:menu:save']"
>保存并发布菜单</el-button
@ -71,7 +77,6 @@
<el-button
class="save_btn"
type="danger"
size="small"
@click="handleDelete"
v-hasPermi="['mp:menu:delete']"
>清空菜单</el-button
@ -82,9 +87,9 @@
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)"
>删除当前菜单<Icon icon="ep:delete"
/></el-button>
<el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)">
删除当前菜单<Icon icon="ep:delete" />
</el-button>
</div>
<div>
<span>菜单名称</span>
@ -161,9 +166,9 @@
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<WxNews :articles="tempObj.replyArticles" />
<el-row class="ope-row" justify="center" align="middle">
<el-button type="danger" circle @click="deleteMaterial"
><icon icon="ep:delete"
/></el-button>
<el-button type="danger" circle @click="deleteMaterial">
<icon icon="ep:delete" />
</el-button>
</el-row>
</div>
<div v-else>
@ -197,33 +202,25 @@
<p>请选择菜单配置</p>
</div>
</div>
</div>
</ContentWrap>
</template>
<script setup>
import { ref, nextTick } from 'vue'
<script setup name="MpMenu">
import { handleTree } from '@/utils/tree'
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
import WxNews from '@/views/mp/components/wx-news/main.vue'
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu'
import { getSimpleAccountList } from '@/api/mp/account'
import { handleTree } from '@/utils/tree'
import * as MpAccountApi from '@/api/mp/account'
import menuOptions from './menuOptions'
const message = useMessage()
const message = useMessage() //
// ======================== ========================
//
const loading = ref(true)
//
const showSearch = ref(true)
// Id
const accountId = ref(undefined)
//
const name = ref('')
const loading = ref(true) //
const accountId = ref(undefined) // Id
const name = ref('') //
const menuList = ref({ children: [] })
const accountList = ref([]) //
// const menuList = ref(menuListData)
// ======================== ========================
const isActive = ref(-1) //
const isSubMenuActive = ref(-1) //
@ -241,11 +238,8 @@ const tempSelfObj = ref({
})
const dialogNewsVisible = ref(false) //
//
const accountList = ref([])
onMounted(async () => {
accountList.value = await getSimpleAccountList()
accountList.value = await MpAccountApi.getSimpleAccountList()
//
if (accountList.value.length > 0) {
// @ts-ignore

View File

@ -1,293 +0,0 @@
<template>
<div class="app-container">
<doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
<!-- 搜索工作栏 -->
<el-form
:model="queryParams"
ref="queryFormRef"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
<el-option
v-for="item in accounts"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input
v-model="queryParams.openid"
placeholder="请输入用户标识"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入昵称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-refresh"
size="small"
@click="handleSync"
v-hasPermi="['mp:user:sync']"
>同步
</el-button>
</el-col>
<!-- <right-toolbar :showSearch="showSearch" @query-table="getList" /> -->
</el-row>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
<el-table-column label="昵称" align="center" prop="nickname" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="标签" align="center" prop="tagIds" width="200">
<template #default="scope">
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
<el-tag>{{ tags.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
</span>
</template>
</el-table-column>
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
<template #default="scope">
<el-tag v-if="scope.row.subscribeStatus === 0" type="success"></el-tag>
<el-tag v-else type="danger">未订阅</el-tag>
</template>
</el-table-column>
<el-table-column label="订阅时间" align="center" prop="subscribeTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.subscribeTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<div class="flex justify-center items-center">
<el-button
type="primary"
link
@click="handleUpdate(scope.row)"
v-hasPermi="['mp:user:update']"
>
<Icon icon="ep:edit" />修改
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="昵称" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="标签" prop="tagIds">
<el-select v-model="form.tagIds" multiple clearable placeholder="请选择标签">
<el-option
v-for="item in tags"
:key="parseInt(item.tagId)"
:label="item.name"
:value="parseInt(item.tagId)"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="MpUser">
import { ref, reactive } from 'vue'
import { updateUser, getUser, getUserPage, syncUser } from '@/api/mp/mpuser'
import { getSimpleAccountList } from '@/api/mp/account'
import { getSimpleTagList } from '@/api/mp/tag'
import { formatDate } from '@/utils/formatTime'
const message = useMessage()
const formRef = ref()
const queryFormRef = ref()
//
const loading = ref(true)
//
const showSearch = ref(true)
//
const total = ref(0)
//
const list = ref([])
//
const title = ref('')
//
const open = ref(false)
//
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: null,
openid: null,
nickname: null
})
//
const form = ref({})
//
const rules = ref({})
//
const accounts = ref([])
//
const tags = ref([])
onMounted(() => {
getSimpleAccountList().then((data) => {
accounts.value = data
//
if (accounts.value.length > 0) {
queryParams.accountId = accounts.value[0].id
}
//
getList()
})
//
getSimpleTagList().then((data) => {
tags.value = data
})
})
/** 查询列表 */
const getList = () => {
//
if (!queryParams.accountId) {
message.error('未选中公众号,无法查询用户')
return false
}
loading.value = true
//
let params = { ...queryParams }
//
getUserPage(params)
.then((data) => {
list.value = data.list
total.value = data.total
})
.finally(() => {
loading.value = false
})
}
/** 取消按钮 */
const cancel = () => {
open.value = false
reset()
}
/** 表单重置 */
const reset = () => {
form.value = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
}
formRef.value?.resetFields()
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
//
if (accounts.value.length > 0) {
queryParams.accountId = accounts.value[0].id
}
handleQuery()
}
/** 修改按钮操作 */
const handleUpdate = (row) => {
reset()
getUser(row.id).then((data) => {
form.value = data
open.value = true
title.value = '修改公众号粉丝'
})
}
/** 提交按钮 */
const submitForm = () => {
formRef.value.validate((valid) => {
if (!valid) {
return
}
//
if (form.value.id != null) {
updateUser(form.value).then(() => {
message.success('修改成功')
open.value = false
getList()
})
}
})
}
/** 同步标签 */
const handleSync = async () => {
const accountId = queryParams.accountId
try {
await message.confirm('是否确认同步粉丝?')
await syncUser(accountId)
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
} catch {}
}
</script>

View File

@ -0,0 +1,99 @@
<template>
<Dialog title="修改" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="formData.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="标签" prop="tagIds">
<el-select v-model="formData.tagIds" multiple clearable placeholder="请选择标签">
<el-option
v-for="item in tagList"
:key="item.tagId"
:label="item.name"
:value="item.tagId"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MpTagApi from '@/api/mp/tag'
import * as MpUserApi from '@/api/mp/user'
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
})
const formRules = reactive({}) //
const formRef = ref() // Ref
const tagList = ref([]) //
/** 打开弹窗 */
const open = async (id: number) => {
dialogVisible.value = true
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await MpUserApi.getUser(id)
} finally {
formLoading.value = false
}
}
//
tagList.value = await MpTagApi.getSimpleTagList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await MpUserApi.updateUser(formData.value)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
}
formRef.value?.resetFields()
}
</script>

187
src/views/mp/user/index.vue Normal file
View File

@ -0,0 +1,187 @@
<template>
<doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
<el-option
v-for="item in accountList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input
v-model="queryParams.openid"
placeholder="请输入用户标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入昵称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
<Icon icon="ep:refresh" class="mr-5px" /> 同步
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
<el-table-column label="昵称" align="center" prop="nickname" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="标签" align="center" prop="tagIds" width="200">
<template #default="scope">
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
<el-tag>{{ tagList.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
</span>
</template>
</el-table-column>
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
<template #default="scope">
<el-tag v-if="scope.row.subscribeStatus === 0" type="success"></el-tag>
<el-tag v-else type="danger">未订阅</el-tag>
</template>
</el-table-column>
<el-table-column
label="订阅时间"
align="center"
prop="subscribeTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
link
@click="openForm(scope.row.id)"
v-hasPermi="['mp:user:update']"
>
修改
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗修改 -->
<UserForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup name="MpUser">
import { dateFormatter } from '@/utils/formatTime'
import * as MpAccountApi from '@/api/mp/account'
import * as MpUserApi from '@/api/mp/user'
import * as MpTagApi from '@/api/mp/tag'
import UserForm from './UserForm.vue'
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: null,
openid: null,
nickname: null
})
const queryFormRef = ref() //
const accountList = ref([]) //
const tagList = ref([]) //
/** 查询列表 */
const getList = async () => {
//
if (!queryParams.accountId) {
message.error('未选中公众号,无法查询用户')
return false
}
try {
loading.value = true
const data = await MpUserApi.getUserPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
//
if (accountList.value.length > 0) {
queryParams.accountId = accountList.value[0].id
}
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (id: number) => {
formRef.value.open(id)
}
/** 同步标签 */
const handleSync = async () => {
const accountId = queryParams.accountId
try {
await message.confirm('是否确认同步粉丝?')
await MpUserApi.syncUser(accountId)
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
await getList()
} catch {}
}
/** 初始化 */
onMounted(async () => {
//
tagList.value = await MpTagApi.getSimpleTagList()
//
accountList.value = await MpAccountApi.getSimpleAccountList()
if (accountList.value.length > 0) {
queryParams.accountId = accountList.value[0].id
}
await getList()
})
</script>

View File

@ -75,7 +75,7 @@
</template>
</XModal>
</template>
<script setup lang="ts" name="App">
<script setup lang="ts" name="PayApp">
import type { FormExpose } from '@/components/Form'
import { rules, allSchemas } from './app.data'
import * as AppApi from '@/api/pay/app'

View File

@ -137,7 +137,7 @@
<!-- 表单弹窗添加/修改 -->
<MerchantForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Merchant">
<script setup lang="ts" name="PayMerchant">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import { dateFormatter } from '@/utils/formatTime'

View File

@ -41,7 +41,7 @@
</template>
</XModal>
</template>
<script setup lang="ts" name="Order">
<script setup lang="ts" name="PayOrder">
import { allSchemas } from './order.data'
import * as OrderApi from '@/api/pay/order'

View File

@ -33,7 +33,7 @@
</template>
</XModal>
</template>
<script setup lang="ts" name="Refund">
<script setup lang="ts" name="PayRefund">
import { allSchemas } from './refund.data'
import * as RefundApi from '@/api/pay/refund'

View File

@ -30,7 +30,7 @@
<!-- 表单弹窗添加/修改 -->
<AreaForm ref="formRef" />
</template>
<script setup lang="tsx" name="Area">
<script setup lang="tsx" name="SystemArea">
import type { Column } from 'element-plus'
import AreaForm from './AreaForm.vue'
import * as AreaApi from '@/api/system/area'

View File

@ -103,7 +103,7 @@
<!-- 表单弹窗添加/修改 -->
<DeptForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Dept">
<script setup lang="ts" name="SystemDept">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { handleTree } from '@/utils/tree'

View File

@ -115,7 +115,7 @@
<!-- 表单弹窗添加/修改 -->
<DictDataForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="DictData">
<script setup lang="ts" name="SystemDictData">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -132,7 +132,7 @@
<DictTypeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="DictType">
<script setup lang="ts" name="SystemDictType">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as DictTypeApi from '@/api/system/dict/dict.type'

View File

@ -137,7 +137,7 @@
<ErrorCodeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="ErrorCode">
<script setup lang="ts" name="SystemErrorCode">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -104,7 +104,7 @@
<!-- 表单弹窗详情 -->
<LoginLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="LoginLog">
<script setup lang="ts" name="SystemLoginLog">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -64,7 +64,7 @@
<!-- 详情弹窗 -->
<MailAccountDetail ref="detailRef" />
</template>
<script setup lang="ts" name="MailAccount">
<script setup lang="ts" name="SystemMailAccount">
import { allSchemas } from './account.data'
import * as MailAccountApi from '@/api/system/mail/account'
import MailAccountForm from './MailAccountForm.vue'

View File

@ -34,7 +34,7 @@
<!-- 表单弹窗详情 -->
<mail-log-detail ref="detailRef" />
</template>
<script setup lang="ts" name="MailLog">
<script setup lang="ts" name="SystemMailLog">
import { allSchemas } from './log.data'
import * as MailLogApi from '@/api/system/mail/log'
import MailLogDetail from './MailLogDetail.vue'

View File

@ -65,7 +65,7 @@
<!-- 表单弹窗发送测试 -->
<MailTemplateSendForm ref="sendFormRef" />
</template>
<script setup lang="ts" name="MailTemplate">
<script setup lang="ts" name="SystemMailTemplate">
import { allSchemas } from './template.data'
import * as MailTemplateApi from '@/api/system/mail/template'
import MailTemplateForm from './MailTemplateForm.vue'

View File

@ -111,7 +111,7 @@
<!-- 表单弹窗添加/修改 -->
<MenuForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Menu">
<script setup lang="ts" name="SystemMenu">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree } from '@/utils/tree'
import * as MenuApi from '@/api/system/menu'

View File

@ -102,7 +102,7 @@
<!-- 表单弹窗添加/修改 -->
<NoticeForm ref="formRef" @success="getList" />
</template>
<script setup lang="tsx">
<script setup lang="tsx" name="SystemNotice">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as NoticeApi from '@/api/system/notice'

View File

@ -153,7 +153,7 @@
<!-- 表单弹窗详情 -->
<NotifyMessageDetail ref="detailRef" />
</template>
<script setup lang="ts" name="NotifyMessage">
<script setup lang="ts" name="SystemNotifyMessage">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as NotifyMessageApi from '@/api/system/notify/message'

View File

@ -115,7 +115,7 @@
<MyNotifyMessageDetail ref="detailRef" />
</template>
<script setup lang="ts" name="MyNotifyMessage">
<script setup lang="ts" name="SystemMyNotify">
import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as NotifyMessageApi from '@/api/system/notify/message'

View File

@ -114,7 +114,7 @@
</template>
</XModal>
</template>
<script setup lang="ts" name="NotifyTemplate">
<script setup lang="ts" name="SystemNotifyTemplate">
import { FormExpose } from '@/components/Form'
// import
import { rules, allSchemas } from './template.data'

View File

@ -119,7 +119,7 @@
<!-- 表单弹窗添加/修改 -->
<ClientForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
<script setup lang="ts" name="SystemOAuth2Client">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as ClientApi from '@/api/system/oauth2/client'

View File

@ -98,7 +98,7 @@
</ContentWrap>
</template>
<script setup lang="ts" name="Oauth2AccessToken">
<script setup lang="ts" name="SystemTokenClient">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'

View File

@ -135,7 +135,7 @@
<!-- 表单弹窗详情 -->
<OperateLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="OperateLog">
<script setup lang="ts" name="SystemOperateLog">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -111,13 +111,12 @@
<!-- 表单弹窗添加/修改 -->
<PostForm ref="formRef" @success="getList" />
</template>
<script setup lang="tsx">
<script setup lang="tsx" name="SystemPost">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as PostApi from '@/api/system/post'
import PostForm from './PostForm.vue'
const message = useMessage() //
const { t } = useI18n() //

View File

@ -152,7 +152,7 @@
<!-- 表单弹窗数据权限 -->
<RoleDataPermissionForm ref="dataPermissionFormRef" />
</template>
<script setup lang="tsx">
<script setup lang="tsx" name="SystemRole">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -143,7 +143,7 @@
<!-- 表单弹窗测试敏感词 -->
<SensitiveWordTestForm ref="testFormRef" />
</template>
<script setup lang="ts" name="SensitiveWord">
<script setup lang="ts" name="SystemSensitiveWordhao">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -129,7 +129,7 @@
<!-- 表单弹窗添加/修改 -->
<SmsChannelForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="SmsChannel">
<script setup lang="ts" name="SystemSmsChannel">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'

View File

@ -184,7 +184,7 @@
<!-- 表单弹窗详情 -->
<SmsLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="smsLog">
<script setup lang="ts" name="SystemSmsLog">
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -211,7 +211,7 @@
<!-- 表单弹窗测试发送 -->
<SmsTemplateSendForm ref="sendFormRef" />
</template>
<script setup lang="ts" name="SmsTemplate">
<script setup lang="ts" name="SystemSmsTemplate">
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'

View File

@ -171,14 +171,13 @@
<!-- 表单弹窗添加/修改 -->
<TenantForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Tenant">
<script setup lang="ts" name="SystemTenant">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as TenantApi from '@/api/system/tenant'
import * as TenantPackageApi from '@/api/system/tenantPackage'
import TenantForm from './TenantForm.vue'
const message = useMessage() //
const { t } = useI18n() //

View File

@ -106,7 +106,7 @@
<!-- 表单弹窗添加/修改 -->
<TenantPackageForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="TenantPackage">
<script setup lang="ts" name="SystemTenantPackage">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as TenantPackageApi from '@/api/system/tenantPackage'

View File

@ -198,7 +198,7 @@
<!-- 分配角色 -->
<UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
</template>
<script setup lang="ts" name="User">
<script setup lang="ts" name="SystemUser">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { checkPermi } from '@/utils/permission'
import { dateFormatter } from '@/utils/formatTime'