v1.0.0-beta.1

This commit is contained in:
全栈小学生 2023-04-15 17:12:49 +08:00
parent b2eb04ef73
commit 0e47055ccb
32728 changed files with 3556401 additions and 0 deletions

16
admin/.env.development Normal file
View File

@ -0,0 +1,16 @@
NODE_ENV = 'development'
# api请求地址
VITE_APP_BASE_URL='/adminapi/'
# 图片服务器地址
VITE_IMG_DOMAIN=''
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中站点的参数名
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
# wap手机端请求地址
VITE_WAP_DOMAIN=''

16
admin/.env.production Normal file
View File

@ -0,0 +1,16 @@
NODE_ENV = 'production'
# api请求地址
VITE_APP_BASE_URL='/adminapi/'
# 图片服务器地址
VITE_IMG_DOMAIN=''
# 请求时header中token的参数名
VITE_REQUEST_HEADER_TOKEN_KEY='token'
# 请求时header中站点的参数名
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
# wap手机端请求地址
VITE_WAP_DOMAIN=''

27
admin/.eslintrc.json Normal file
View File

@ -0,0 +1,27 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:vue/vue3-essential",
"standard-with-typescript",
"eslint:recommended"
],
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"parser": "@typescript-eslint/parser"
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {
"no-tabs":"off",
"indent": [1, 4, { "SwitchCase": 1 }],
"eqeqeq":"off"
}
}

24
admin/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
admin/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

18
admin/README.md Normal file
View File

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

5
admin/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

78
admin/components.d.ts vendored Normal file
View File

@ -0,0 +1,78 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Attachment: typeof import('./src/components/upload-attachment/attachment.vue')['default']
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
Editor: typeof import('./src/components/editor/index.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/icon/index.vue')['default']
PopoverInput: typeof import('./src/components/popover-input/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectArea: typeof import('./src/components/select-area/index.vue')['default']
SelectIcon: typeof import('./src/components/select-icon/index.vue')['default']
UploadAttachment: typeof import('./src/components/upload-attachment/index.vue')['default']
UploadFile: typeof import('./src/components/upload-file/index.vue')['default']
UploadImage: typeof import('./src/components/upload-image/index.vue')['default']
UploadVideo: typeof import('./src/components/upload-video/index.vue')['default']
VideoPlayer: typeof import('./src/components/video-player/index.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

13
admin/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image" href="/niucloud.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理端</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

11486
admin/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

51
admin/package.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "admin",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@vueuse/core": "^9.12.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.3.1",
"css-color-function": "^1.3.3",
"echarts": "^5.4.1",
"element-plus": "^2.2.29",
"nprogress": "^0.2.0",
"pinia": "^2.0.30",
"qrcode": "^1.5.1",
"sass": "^1.58.0",
"sortablejs": "^1.15.0",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6",
"vue3-video-play": "^1.3.1-beta.6"
},
"devDependencies": {
"@tailwindcss/line-clamp": "^0.4.2",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13",
"eslint": "^8.34.0",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.9.0",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"unplugin-auto-import": "^0.13.0",
"unplugin-vue-components": "^0.23.0",
"vite": "^4.1.0",
"vue-tsc": "^1.0.24"
}
}

6
admin/postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

BIN
admin/public/niucloud.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

40
admin/src/App.vue Normal file
View File

@ -0,0 +1,40 @@
<template>
<el-config-provider :locale="locale">
<router-view></router-view>
</el-config-provider>
</template>
<script lang="ts" setup>
import { computed, onMounted, watch } from 'vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import en from 'element-plus/dist/locale/en.mjs'
import useSystemStore from '@/stores/modules/system'
import useAppStore from '@/stores/modules/app'
import { useDark, useToggle } from '@vueuse/core'
import { setThemeColor } from '@/utils/common'
import { language } from '@/lang'
import { useRoute } from 'vue-router'
const route = useRoute()
//
const systemStore = useSystemStore()
const locale = computed(() => (systemStore.lang === 'zh-cn' ? zhCn : en))
language.loadLocaleMessages(route.path, systemStore.lang)
const toggleDark = useToggle(useDark())
watch(route, () => {
useAppStore().$patch(state => {
state.route = route.path
})
})
onMounted(() => {
//
toggleDark(systemStore.dark)
setThemeColor(systemStore.theme, systemStore.dark ? 'dark' : 'light')
})
</script>
<style lang="scss" scoped></style>

26
admin/src/api/aliapp.ts Normal file
View File

@ -0,0 +1,26 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getAliappConfig() {
return request.get('aliapp/config')
}
/**
*
* @param params
* @returns
*/
export function setAliappConfig(params: Record<string, any>) {
return request.put('aliapp/config', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getAliappStatic() {
return request.get('aliapp/static')
}

105
admin/src/api/article.ts Normal file
View File

@ -0,0 +1,105 @@
import request from '@/utils/request'
/***************************************************** 文章表 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getArticleList(params: Record<string, any>) {
return request.get(`article/article`, { params })
}
/**
*
* @param id id
* @returns
*/
export function getArticleInfo(id: number) {
return request.get(`article/article/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addArticle(params: Record<string, any>) {
return request.post('article/article', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateArticle(params: Record<string, any>) {
return request.put(`article/article/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteArticle(id: number) {
return request.delete(`article/article/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 文章分类管理 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getArticleCategoryList(params: Record<string, any>) {
return request.get(`article/category`, { params })
}
/**
*
* @param params
* @returns
*/
export function getArticleCategoryAll(params: Record<string, any>) {
return request.get(`article/category/all`, params)
}
/**
*
* @param id id
* @returns
*/
export function getArticleCategoryInfo(category_id: number) {
return request.get(`article/category/${category_id}`);
}
/**
*
* @param params
* @returns
*/
export function addArticleCategory(params: Record<string, any>) {
return request.post('article/category', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function updateArticleCategory(params: Record<string, any>) {
return request.put(`article/category/${params.category_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id id
* @returns
*/
export function deleteArticleCategory(category_id: number) {
return request.delete(`article/category/${category_id}`, { showErrorMessage: true, showSuccessMessage: true });
}

26
admin/src/api/auth.ts Normal file
View File

@ -0,0 +1,26 @@
import request from '@/utils/request'
/**
*
* @param params
* @returns
*/
export function login(params: Record<string, any>) {
return request.get('login', { params, showErrorMessage: true })
}
/**
*
* @returns
*/
export function getAuthMenus() {
return request.get('auth/authmenu')
}
/**
*
* @returns
*/
export function getSiteInfo() {
return request.get('auth/site')
}

117
admin/src/api/diy.ts Normal file
View File

@ -0,0 +1,117 @@
import request from '@/utils/request'
/***************************************************** 自定义页面 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getDiyPageList(params: Record<string, any>) {
return request.get(`diy/diy`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getDiyPageInfo(id: number) {
return request.get(`diy/diy/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addDiyPage(params: Record<string, any>) {
return request.post('diy/diy', params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param params
*/
export function updateDiyPage(params: Record<string, any>) {
return request.put(`diy/diy/${params.id}`, params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
* 使
* @param params
*/
export function setUseDiyPage(params: Record<string, any>) {
return request.put(`diy/use`, params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param params
*/
export function updateDiyPageShare(params: Record<string, any>) {
return request.put(`diy/diy/share`, params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param id
* @returns
*/
export function deleteDiyPage(id: number) {
return request.delete(`diy/diy/${id}`, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
*/
export function initPage(params: Record<string, any>) {
return request.get(`diy/init`, {params})
}
/**
*
*/
export function getLink(params: Record<string, any>) {
return request.get(`diy/link`, {params})
}
/**
*
*/
export function getDiyBottom(params: Record<string, any>) {
return request.get(`diy/bottom`, {params})
}
/**
*
* @param params
* @returns
*/
export function setDiyBottom(params: Record<string, any>) {
return request.post('diy/bottom', params, {showErrorMessage: true, showSuccessMessage: true})
}
/**
*
*/
export function getDiyPageType(params: Record<string, any>) {
return request.get(`diy/type`, {params})
}
/**
*
* @param params
* @returns
*/
export function getDiyRouteList(params: Record<string, any>) {
return request.get(`diy/route`, {params})
}
/**
*
* @param params
*/
export function updateDiyRouteShare(params: Record<string, any>) {
return request.put(`diy/route/share`, params, {showErrorMessage: true, showSuccessMessage: true})
}

18
admin/src/api/h5.ts Normal file
View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
/**
* h5配置
* @returns
*/
export function getH5Config() {
return request.get('channel/h5/config')
}
/**
* h5配置
* @param params
* @returns
*/
export function setH5Config(params: Record<string, any>) {
return request.put('channel/h5/config', params, { showErrorMessage: true, showSuccessMessage: true })
}

219
admin/src/api/member.ts Normal file
View File

@ -0,0 +1,219 @@
import request from '@/utils/request'
/***************************************************** 会员管理 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getMemberList(params: Record<string, any>) {
return request.get(`member/member`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getMemberInfo(id: number) {
return request.get(`member/member/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addMember(params: Record<string, any>) {
return request.post(`member/member`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getRegisterType(params: Record<string, any>) {
return request.get(`member/registertype`, params)
}
/***************************************************** 会员标签 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getMemberLabelList(params: Record<string, any>) {
return request.get(`member/label`, {params})
}
/**
*
* @param label_id label_id
* @returns
*/
export function getMemberLabelInfo(label_id: number) {
return request.get(`member/label/${label_id}`);
}
/**
*
* @param params
* @returns
*/
export function addMemberLabel(params: Record<string, any>) {
return request.post('member/label', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param label_id
* @param params
* @returns
*/
export function updateMemberLabel(params: Record<string, any>) {
return request.put(`member/label/${params.label_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param label_id
* @returns
*/
export function deleteMemberLabel(label_id: number) {
return request.delete(`member/label/${label_id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param label_id label_id
* @returns
*/
export function getMemberLabelAll() {
return request.get(`member/label/all`);
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateMemberDetail(params: Record<string, any>) {
return request.put(`member/member/modify/${params.member_id}/${params.field}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 会员零钱 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getMoneyList(params: Record<string, any>) {
return request.get(`member/account/money`, {params})
}
/***************************************************** 会员账户 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getChangeTypeList(change_type: string) {
return request.get(`member/account/change_type/${change_type}`)
}
/**
*
* @param params
* @returns
*/
export function getPointList(params: Record<string, any>) {
return request.get(`member/account/point`, { params })
}
/**
*
* @param params
* @returns
*/
export function getBalanceList(params: Record<string, any>) {
return request.get(`member/account/balance`, { params })
}
/**
*
* @param params
* @returns
*/
export function adjustPoint(params: Record<string, any>) {
return request.post(`member/account/point`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function adjustBalance(params: Record<string, any>) {
return request.post(`member/account/balance`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 会员相关设置 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getLoginConfig(params: Record<string, any>) {
return request.get(`member/config/login`, params)
}
/**
*
* @param params
* @returns
*/
export function setLoginConfig(params: Record<string, any>) {
return request.post(`member/config/login`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getWithdrawConfig() {
return request.get(`member/config/withdraw`)
}
/**
*
* @param params
* @returns
*/
export function setWithdrawConfig(params: Record<string, any>) {
return request.post(`member/config/withdraw`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getWithdrawList(params: Record<string, any>) {
return request.get(`member/withdraw`, {params})
}
/**
*
* @param params
* @returns
*/
export function getTransfertype() {
return request.get(`member/withdraw/transfertype`)
}

75
admin/src/api/message.ts Normal file
View File

@ -0,0 +1,75 @@
import request from '@/utils/request'
/***************************************************** 消息管理 ****************************************************/
/**
*
* @returns
*/
export function getMessageList() {
return request.get('message/message')
}
/**
*
* @param params
* @returns
*/
export function getMessageInfo(key: string) {
return request.get(`message/message/${key}`)
}
/**
*
* @param params
* @returns
*/
export function getMessageLog(params: any) {
return request.get(`message/log`, { params })
}
/**
*
* @param params
* @returns
*/
export function updateMessageStatus(params: Record<string, any>) {
return request.post(`message/message/updatestatus`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function updateMessage(params: Record<string, any>) {
return request.post(`message/message/update`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getSmsList() {
return request.get('message/message/sms')
}
/**
*
* @param sms_type
* @returns
*/
export function getSmsInfo(sms_type: string) {
return request.get(`message/message/sms/${sms_type}`,)
}
/**
*
* @param sms_type
* @param params
* @returns
*/
export function updateSms(params: Record<string, any>) {
return request.put(`message/message/sms/${params.sms_type}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

29
admin/src/api/order.ts Normal file
View File

@ -0,0 +1,29 @@
import request from '@/utils/request'
/***************************************************** 充值订单 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getRechargeOrderList(params: Record<string, any>) {
return request.get(`order/recharge`, { params })
}
/**
*
* @param order_id
* @returns
*/
export function getRechargeOrderInfo(order_id: number) {
return request.get(`order/recharge/${order_id}`);
}
/**
*
* @returns
*/
export function getRechargeOrderStatusList() {
return request.get(`order/recharge/status`)
}

18
admin/src/api/personal.ts Normal file
View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getUserInfo(type:string) {
return request.get(`auth/get`)
}
/**
*
* @returns
*/
export function setUserInfo(params: Record<string, any>) {
return request.put(`auth/update`, params, { showErrorMessage: true, showSuccessMessage: true });
}

199
admin/src/api/site.ts Normal file
View File

@ -0,0 +1,199 @@
import request from '@/utils/request'
//包含站点管理,站点用户管理,站点操作日志
/***************************************************** 站点管理 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getSiteList(params: Record<string, any>) {
return request.get(`site/site`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getSiteInfo(site_id: number) {
return request.get(`site/site/${site_id}`);
}
/**
*
* @param params
* @returns
*/
export function addSite(params: Record<string, any>) {
return request.post('site/site', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateSite(params: Record<string, any>) {
return request.put(`site/site/${params.site_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getStatusList() {
return request.get(`site/statuslist`)
}
/***************************************************** 站点分组管理 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getSiteGroupList(params: Record<string, any>) {
return request.get(`site/group`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getSiteGroupInfo(site_id: number) {
return request.get(`site/group/${site_id}`);
}
/**
*
* @param params
* @returns
*/
export function addSiteGroup(params: Record<string, any>) {
return request.post('site/group', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateSiteGroup(params: Record<string, any>) {
return request.put(`site/group/${params.group_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function deleteSiteGroup(group_id: number) {
return request.delete(`site/group/${group_id}`, { showErrorMessage: true, showSuccessMessage: true });
}
/**
*
* @param params
* @returns
*/
export function getSiteGroupAll(params: Record<string, any>) {
return request.get(`site/group/all`, params)
}
/***************************************************** 当前站点用户 *************************************************/
/**
*
* @param params
* @returns
*/
export function getUserList(params: Record<string, any>) {
return request.get(`site/user`, { params })
}
/**
*
* @param id id
* @returns
*/
export function getUserInfo(uid: number) {
return request.get(`site/user/${uid}`);
}
/**
*
* @param params
* @returns
*/
export function addUser(params: Record<string, any>) {
return request.post('site/user', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param uid
* @param params
* @returns
*/
export function updateUser(params: Record<string, any>) {
return request.put(`site/user/${params.uid}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param uid
* @returns
*/
export function deleteUser(uid: number) {
return request.delete(`site/user/${uid}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param uid
* @returns
*/
export function lockUser(uid: number) {
return request.put(`site/user/lock/${uid}`)
}
/**
*
* @param uid
* @returns
*/
export function unlockUser(uid: number) {
return request.put(`site/user/unlock/${uid}`)
}
/***************************************************** 操作日志 **************************************************/
/**
*
* @param params
* @returns
*/
export function getLogList(params: Record<string, any>) {
return request.get(`site/log`, { params })
}
/**
*
* @param params
* @returns
*/
export function getLogInfo(id: number) {
return request.get(`site/log/${id}`)
}

20
admin/src/api/stat.ts Normal file
View File

@ -0,0 +1,20 @@
import request from '@/utils/request'
/***************************************************** 统计信息 **************************************************/
/**
*
* @param params
* @returns
*/
export function getStatInfo() {
return request.get(`stat/index`)
}
/**
*
* @param params
* @returns
*/
export function getSiteStatInfo() {
return request.get(`stat/siteindex`)
}

367
admin/src/api/sys.ts Normal file
View File

@ -0,0 +1,367 @@
import request from '@/utils/request'
/***************************************************** 系统整体信息 *************************************************/
/**
*
* @returns
*/
export function getInfo() {
return request.get('sys/role')
}
/***************************************************** 用户组 ****************************************************/
/**
*
* @returns
*/
export function getRoleList(params: Record<string, any>) {
return request.get('sys/role', { params })
}
/**
*
* @param params
* @returns
*/
export function getRoleInfo(roleId: number) {
return request.get(`sys/role/${roleId}`)
}
/**
*
* @param params
* @returns
*/
export function addRole(params: Record<string, any>) {
return request.post(`sys/role`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param role_id
* @param params
* @returns
*/
export function updateRole(params: Record<string, any>) {
return request.put(`sys/role/${params.role_id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param role_id
* @returns
*/
export function deleteRole(roleId: number) {
return request.delete(`sys/role/${roleId}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function allRole() {
return request.get('sys/role/all')
}
/***************************************************** 全部菜单 ****************************************************/
/**
*
* @returns
*/
export function getMenus(type:string) {
return request.get(`sys/menu/${type}`)
}
/**
*
* @param id
* @returns
*/
export function getMenuInfo(menu_key: string) {
return request.get(`sys/menu/info/${menu_key}`);
}
/**
*
* @param params
* @returns
*/
export function addMenu(params: Record<string, any>) {
return request.post('sys/menu', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateMenu(params: Record<string, any>) {
return request.put(`sys/menu/${params.menu_key}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteMenu(menu_key: string) {
return request.delete(`sys/menu/${menu_key}`, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 站点菜单 ****************************************************/
/**
*
* @returns
*/
export function getSiteMenus() {
return request.get(`site/site/menu`)
}
/***************************************************** 设置 ****************************************************/
/**
*
* @returns
*/
export function getWebsite() {
return request.get('sys/config/website')
}
/**
*
* @param params
* @returns
*/
export function setWebsite(params: Record<string, any>) {
return request.put(`sys/config/website`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getCopyright() {
return request.get('sys/config/copyright')
}
/**
*
* @param params
* @returns
*/
export function setCopyright(params: Record<string, any>) {
return request.put(`sys/config/copyright`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getAttachmentCategoryList(params: Record<string, any>) {
return request.get(`sys/attachment/category`, { params })
}
/**
*
* @param params
*/
export function addAttachmentCategory(params: Record<string, any>) {
return request.post(`sys/attachment/category`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function updateAttachmentCategory(params: Record<string, any>) {
return request.put(`sys/attachment/category/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteAttachmentCategory(id: number) {
return request.delete(`sys/attachment/category/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function getAttachmentList(params: Record<string, any>) {
return request.get(`sys/attachment`, { params })
}
/**
*
* @param params
* @returns
*/
export function deleteAttachment(params: Record<string, any>) {
return request.delete(`sys/attachment/del`, { data: params, showErrorMessage: true, showSuccessMessage: true})
}
/**
*
* @param params
* @returns
*/
export function moveAttachment(params: Record<string, any>) {
return request.put(`sys/attachment/batchmove`, params)
}
/***************************************************** 地址管理 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getAreaListByPid(pid: number = 0) {
return request.get(`sys/area/list_by_pid/${pid}`)
}
/**
*
* @param params
* @returns
*/
export function getAreatree(level: number = 1) {
return request.get(`sys/area/tree/${level}`)
}
/***************************************************** 存储设置 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getStorageList() {
return request.get(`sys/storage`)
}
/**
*
* @param params
* @returns
*/
export function getStorageInfo(type: string) {
return request.get(`sys/storage/${type}`)
}
/**
*
* @param params
* @returns
*/
export function updateStorage(params: Record<string, any>) {
return request.put(`sys/storage/${params.storage_type}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/***************************************************** 支付设置 ****************************************************/
/**
*
* @returns
*/
export function getPayConfig(type:string) {
return request.get(`pay/config/${type}`)
}
/**
*
* @returns
*/
export function setPayConfig(params: Record<string, any>) {
return request.put(`pay/config/${params.type}`, params, { showErrorMessage: true, showSuccessMessage: true });
}
/**
*
* @returns
*/
export function getPayList() {
return request.get(`pay/lists`)
}
/***************************************************** 定时任务 ****************************************************/
/**
*
* @returns
*/
export function getCronList(params:any) {
return request.get(`sys/cron`, { params })
}
/**
*
* @returns
*/
export function getCronInfo(id:string) {
return request.get(`sys/cron/${id}`);
}
/**
*
* @returns
*/
export function getCronType() {
return request.get(`sys/cron/type`)
}
/***************************************************** 协议管理 ****************************************************/
/**
*
* @returns
*/
export function getAgreementList() {
return request.get(`sys/agreement`)
}
/**
*
* @returns
*/
export function getAgreementInfo(key:string) {
return request.get(`sys/agreement/${key}`);
}
/**
*
* @returns
*/
export function updateAgreement(params: Record<string, any>) {
return request.put(`sys/agreement/${params.key}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getChannelType() {
return request.get(`sys/channel`);
}
/**
*
* @returns
*/
export function getSceneDomain() {
return request.get(`sys/scene_domain`);
}

68
admin/src/api/tools.ts Normal file
View File

@ -0,0 +1,68 @@
import request from '@/utils/request'
/***************************************************** 代码生成 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getGenerateTableList(params: Record<string, any>) {
return request.get(`generator/generator`, {params})
}
/**
*
* @param id id
* @returns
*/
export function getGenerateTableInfo(id: number) {
return request.get(`generator/generator/${id}`);
}
/**
*
* @param params
* @returns
*/
export function addGenerateTable(params: Record<string, any>) {
return request.post('generator/generator', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateGenerateTable(params: Record<string, any>) {
return request.put(`generator/generator/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @returns
*/
export function deleteGenerateTable(id: number) {
return request.delete(`generator/generator/${id}`, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param params
* @returns
*/
export function generateCreate(params: Record<string, any>) {
return request.post(`generator/download`, params)
}
/**
*
* @param file
* @returns
*/
export function generateTable() {
return request.get(`generator/table`)
}

43
admin/src/api/user.ts Normal file
View File

@ -0,0 +1,43 @@
import request from '@/utils/request'
//当前接口用户指系统整体用户管理,站内用户添加,编辑,详情,操作日志,请查看站点内部相关接口
/***************************************************** 用户 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getUserList(params: Record<string, any>) {
return request.get(`user/user`, { params })
}
/**
*
* @param uid uid
* @returns
*/
export function getUserInfo(uid: number) {
return request.get(`user/user/${uid}`);
}
/**
*
* @param params
* @returns
*/
export function addUser(params: Record<string, any>) {
return request.post('user/user', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @param id
* @param params
* @returns
*/
export function updateUser(params: Record<string, any>) {
return request.put(`user/user/${params.uid}`, params, { showErrorMessage: true, showSuccessMessage: true })
}

35
admin/src/api/weapp.ts Normal file
View File

@ -0,0 +1,35 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getWeappConfig() {
return request.get('weapp/config')
}
/**
*
* @param params
* @returns
*/
export function setWeappConfig(params: Record<string, any>) {
return request.put('weapp/config', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getTemplateList() {
return request.get('weapp/template')
}
/**
*
* @param params
* @returns
*/
export function getBatchAcquisition(params: Record<string, any>) {
return request.put('weapp/template/sync', params, { showErrorMessage: true, showSuccessMessage: true })
}

66
admin/src/api/wechat.ts Normal file
View File

@ -0,0 +1,66 @@
import request from '@/utils/request'
/**
*
* @returns
*/
export function getWechatConfig() {
return request.get('wechat/config')
}
/**
*
* @param uid uid
* @returns
*/
export function getWechatStatic() {
return request.get('wechat/static');
}
/**
*
* @param params
* @returns
*/
export function updateWechatConfig(params: Record<string, any>) {
return request.put('wechat/config', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getWechatMenu() {
return request.get('wechat/menu')
}
/**
*
* @param params
* @returns
*/
export function updateWechatMenu(params: Record<string, any>) {
return request.put('wechat/menu', params, { showErrorMessage: true, showSuccessMessage: true })
}
/**
*
* @returns
*/
export function getTemplateList() {
return request.get('wechat/template')
}
/**
*
* @param params
* @returns
*/
export function getBatchAcquisition(params: Record<string, any>) {
return request.put('wechat/template/sync', params, { showErrorMessage: true, showSuccessMessage: true })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

1
admin/src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,187 @@
<template>
<div>
<div @click="show">
<slot>
<el-input v-model="value.title" :placeholder="t('linkPlaceholder')" readonly>
<template #suffix>
<div @click.stop="clear">
<el-icon v-if="value.name">
<Close/>
</el-icon>
<el-icon v-else>
<ArrowRight/>
</el-icon>
</div>
</template>
</el-input>
</slot>
</div>
<el-dialog v-model="showDialog" :title="t('selectLinkTips')" width="40%" :close-on-press-escape="false"
:destroy-on-close="true" :close-on-click-modal="false">
<div class="flex items-start">
<el-scrollbar class="w-[140px] border-r h-[350px]">
<div v-for="(item,index) in link" :key="index"
class="h-[40px] leading-[40px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
:class="[ item.name == parentLinkName ? 'bg-primary-light-9 text-primary' : '' ]"
@click="changeParentLink(item)">
{{ item.title }}
</div>
</el-scrollbar>
<el-scrollbar class="pl-4 h-[350px] flex-1">
<template v-if="parentLinkName == 'DIY_LINK'">
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkName')">
<el-input v-model="selectLink.title" :placeholder="t('diyLinkNamePlaceholder')"
class="w-6/12"/>
</el-form-item>
</div>
<div class="mb-[16px]">
<el-form-item :label="t('diyLinkUrl')">
<el-input v-model="selectLink.url" :placeholder="t('diyLinkUrlPlaceholder')"
class="w-6/12"/>
</el-form-item>
</div>
<el-form-item label=" ">
<div class="text-sm text-gray-400 select-text">路径必须以/开头/pages/index/index</div>
</el-form-item>
<el-form-item label=" ">
<div class="text-sm text-gray-400 select-text">跳转外部链接/开头https://baidu.com</div>
</el-form-item>
</template>
<div v-else class="flex flex-wrap">
<div v-for="(item,index) in childList" :key="index"
class="border border-br rounded-[3px] mr-[10px] mb-[10px] px-4 h-[32px] leading-[32px] cursor-pointer hover:bg-primary-light-9 px-[10px] hover:text-primary"
:class="[ item.name == selectLink.name ? 'border-primary text-primary' : '' ]"
@click="changeChildLink(item)">{{ item.title }}
</div>
</div>
</el-scrollbar>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel')}}</el-button>
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import {t} from '@/lang'
import {ref, computed} from 'vue'
import {cloneDeep} from 'lodash-es'
import {getLink} from '@/api/diy';
import {ElMessage} from 'element-plus'
const prop = defineProps({
modelValue: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const value: any = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const showDialog = ref(false)
const link: any = ref([])
const parentLinkName = ref('')
const childList: any = ref([])
const selectLink: any = ref([])
const show = () => {
//
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value);
parentLinkName.value = selectLink.value.parent;
}
showDialog.value = true
}
getLink({}).then((res: any) => {
if (res.code == 200) {
link.value = res.data;
childList.value = link.value[0].child_list;
if (value.value.name != '') {
selectLink.value = cloneDeep(value.value);
} else {
selectLink.value = {
parent: link.value[0].name
};
}
parentLinkName.value = selectLink.value.parent;
}
});
//
const changeParentLink = (item: any) => {
childList.value = item.child_list;
parentLinkName.value = item.name;
}
//
const changeChildLink = (item: any) => {
delete item.is_share;
Object.assign(selectLink.value, item)
}
const clear = () => {
value.value = {
name: '',
parent: '',
title: '',
url: ''
}
}
const save = () => {
//
if (parentLinkName.value === 'DIY_LINK') {
selectLink.value.parent = parentLinkName.value
selectLink.value.name = parentLinkName.value
if (!selectLink.value.title) {
ElMessage({
message: t('diyLinkNameNotEmpty'),
type: 'warning',
})
return;
}
if (!selectLink.value.url) {
ElMessage({
message: t('diyLinkUrlNotEmpty'),
type: 'warning',
})
return;
}
}
value.value = cloneDeep(selectLink.value)
showDialog.value = false
}
defineExpose({
showDialog
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,101 @@
<template>
<div class="border border-color">
<toolbar class="border-b border-color" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
<wang-editor :style="{ height, 'overflow-y': 'hidden', width: '100%' }" :defaultConfig="editorConfig" :mode="mode"
v-model="content" @onCreated="handleCreated" />
<upload-attachment type="image" ref="imageRef" :limit="10" @confirm="imageSelect" />
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
</div>
</template>
<script lang="ts" setup>
import { shallowRef, computed, onBeforeUnmount, ref } from 'vue'
import '@wangeditor/editor/dist/css/style.css'
import { IToolbarConfig, IEditorConfig, IDomEditor } from '@wangeditor/editor'
import { Editor as WangEditor, Toolbar } from '@wangeditor/editor-for-vue'
import { img } from '@/utils/common'
const editorRef = shallowRef()
const prop = defineProps({
modelValue: {
type: String,
default: ''
},
mode: {
type: String,
default: 'simple'
},
height: {
type: String,
default: '300px'
}
})
const emit = defineEmits(['update:modelValue'])
const imageRef: Record<string, any> | null = ref(null)
const videoRef: Record<string, any> | null = ref(null)
const content = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
//
const toolbarConfig: Partial<IToolbarConfig> = {
excludeKeys: ['group-image', 'insertVideo', 'insertLink'],
insertKeys: {
index: 16,
keys: ['uploadImage', 'uploadVideo']
}
}
//
type InsertFnType = (url: string) => void
let insertFn: InsertFnType = (url: string) => { }
const editorConfig: Partial<IEditorConfig> = {
MENU_CONF: {
uploadImage: {
customBrowseAndUpload(insert: InsertFnType) {
imageRef.value.showDialog = true
insertFn = insert
}
},
uploadVideo: {
customBrowseAndUpload(insert: InsertFnType) {
videoRef.value.showDialog = true
insertFn = insert
}
}
}
}
const imageSelect = (data: Record<string, any>) => {
data.forEach((item: any) => { insertFn(img(item.url)) })
}
const videoSelect = (data: Record<string, any>) => {
insertFn(img(data.url))
}
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
}
/**
* 组件销毁时也及时销毁编辑器
*/
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,42 @@
<script lang="ts">
import { createVNode, resolveComponent, defineComponent } from 'vue'
export default defineComponent({
name: 'Icon',
props: {
name: {
type: String,
required: true
},
color: {
type: String,
default: 'var(--color)'
},
class: {
type: [String, Object],
default: ''
},
size: {
type: String,
default: '16px'
},
},
setup(props) {
let [type, name] = props.name.split(/-(.*)/)
let style = {
color: props.color,
fontSize: props.size
}
switch (type) {
case 'element':
return () => createVNode('el-icon', { class: ['icon el-icon', props.class], style: style }, [createVNode(resolveComponent(name))])
break;
case 'iconfont':
return () => createVNode('i', { class: [name, 'iconfont', props.class], style: style })
break;
}
}
})
</script>

View File

@ -0,0 +1,64 @@
<template>
<el-popover placement="top" trigger="click" :width="prop.width" v-model:visible="visible">
<el-input v-model="value" :placeholder="prop.placeholder" clearable class="mr-[10px]" :maxlength="prop.maxlength"
:show-word-limit="true" />
<div class="text-right mt-[15px]">
<el-button @click="visible = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm">{{ t('confirm') }}</el-button>
</div>
<template #reference>
<slot></slot>
</template>
</el-popover>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { t } from '@/lang'
import { ElMessage } from 'element-plus'
const prop = defineProps({
width: {
type: String,
default: '350px'
},
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
required: {
type: Boolean,
default: true
},
maxlength: {
type: Number,
default: 10
}
})
const value = ref(prop.value)
const visible = ref(false)
watch(visible, () => {
if (!visible.value) {
value.value = ''
}
})
const emit = defineEmits(['confirm'])
const confirm = () => {
if (!/[\S]+/.test(value.value)) {
ElMessage.error(prop.placeholder || '不能为空')
return
}
emit('confirm', value.value)
visible.value = false
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,174 @@
<template>
<div class="area-component">
<!-- -->
<el-select
:placeholder="t('provincePlaceholder')"
v-model="state.province"
clearable
@change="changeArea('province')"
>
<el-option
v-for="item in state.provinceList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<!-- -->
<el-select
:placeholder="t('cityPlaceholder')"
style="margin: 0 10px;"
:disabled="!state.province"
v-model="state.city"
clearable
@change="changeArea('city')"
>
<el-option
v-for="item in state.citiesList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<!-- -->
<el-select
:placeholder="t('districtPlaceholder')"
:disabled="!state.province || !state.city"
v-model="state.area"
clearable
@change="changeArea('area')"
>
<el-option
v-for="item in state.areasList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
</template>
<script lang="ts" setup>
import { reactive, onBeforeMount, computed, watch,ref, toRaw,onMounted } from 'vue'
import { getAreaListByPid } from '@/api/sys'
import { t } from '@/lang'
import { da } from 'element-plus/es/locale'
//
export interface areaType {
id: string
name: string
pid: number
children?: areaType[]
}
const prop = defineProps({
initData: {
type: Object,
default: {
province : '',
city : '',
area : ''
}
}
})
const emits = defineEmits<{
(e: 'areaChange', value: any):void
}>()
const state = reactive({
//
provinceList: [] as areaType[],
citiesList: [] as areaType[],
areasList: [] as areaType[],
//
province: "",
city: "",
area: ""
})
onBeforeMount(async ()=>{
state.provinceList = (await getAreaListByPid(0)).data
})
watch( () => prop.initData.province, async (val) => {
state['province'] = prop.initData['province']
state['city'] = prop.initData['city']
state['area'] = prop.initData['area']
state.citiesList = (await getAreaListByPid(parseInt(state.province))).data
state.areasList = (await getAreaListByPid(parseInt(state.city))).data
emitsArea();
})
watch(()=>state.area, (val)=>{
if(val){
emitsArea();
}
})
watch(()=>state.province, (val)=>{
if(val){
emitsArea();
}
})
watch(()=>state.city, (val)=>{
if(val){
emitsArea();
}
})
const emitsArea = () => {
const paramsData = {
province: {} as areaType,
city: {} as areaType,
area: {} as areaType
}
let tmp = state.provinceList.find((item) => item.id === state.province)
paramsData.province.name = tmp? tmp.name : ""
paramsData.province.id = tmp? tmp.id : ""
tmp = state.citiesList.find((item) => item.id === state.city) as any
paramsData.city.name = tmp? tmp.name : ""
paramsData.city.id = tmp ?tmp.id : ""
tmp = state.areasList.find((item) => item.id === state.area) as any
paramsData.area.name = tmp ? tmp.name : ""
paramsData.area.id = tmp ? tmp.id : ""
emits('areaChange', paramsData)
}
const changeArea = async (data : any) => {
if(data == 'province'){
state.city = ""
state.area = ""
if(!state.province) {
emitsArea();
return false;
}
state.citiesList = (await getAreaListByPid(parseInt(state.province))).data
}
if(data == 'city'){
state.area = ""
if(!state.city) {
emitsArea();
return false;
}
state.areasList = (await getAreaListByPid(parseInt(state.city))).data
}
if(data == 'area'){
if(!state.area) {
emitsArea();
return false;
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,75 @@
<template>
<el-popover trigger="click" v-model:visible="visible">
<template #reference>
<slot name="reference"></slot>
</template>
<div class="flex w-full flex-col">
<div class="head flex w-full mb-[10px]">
<span>请选择图标</span>
<div class="flex justify-end flex-auto">
<span class="ml-[10px] cursor-pointer" :class="{ active: type == 'element' }"
@click="type = 'element'">element</span>
<span class="ml-[10px] cursor-pointer" :class="{ active: type == 'iconfont' }"
@click="type = 'iconfont'">iconfont</span>
</div>
</div>
<div class="icon-wrap h-[240px]">
<el-scrollbar>
<div class="flex flex-wrap" v-show="type == 'element'">
<el-button v-for="icon in element" class="w-[35px] h-[35px] icon-item"
@click="selectIcon('element-' + icon)">
<icon :name="'element-' + icon" />
</el-button>
</div>
<div class="flex flex-wrap" v-show="type == 'iconfont'">
<el-button v-for="icon in iconfont" class="w-[35px] h-[35px] icon-item"
@click="selectIcon('iconfont-' + icon)">
<icon :name="'iconfont-' + icon" />
</el-button>
</div>
</el-scrollbar>
</div>
</div>
</el-popover>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
let type = ref('element')
let visible = ref('false')
// element
const element = computed(() => {
return Object.keys(ElementPlusIconsVue)
})
// iconfont
const iconfont = computed(() => {
let iconfile = import.meta.globEager('@/styles/iconfont.css')['/src/styles/iconfont.css'].default
let icons = Array.from(iconfile.matchAll(/(icon.*)\:before/g))
return icons.map(item => {
return item[1]
})
})
const emit = defineEmits(['select'])
//
const selectIcon = (name) => {
emit('select', name)
visible.value = false
}
</script>
<style lang="scss" scoped>
.icon-item {
margin: 6px 6px 6px 0;
}
.active {
color: var(--el-color-primary);
}
</style>

View File

@ -0,0 +1,512 @@
<template>
<div class="flex border-t border-b main-wrap border-color w-full" :class="scene == 'select' ? 'h-[40vh]' : 'h-full'">
<!-- 分组 -->
<div class="group-wrap w-[180px] p-[15px] h-full border-r border-color flex flex-col">
<el-input v-model="categoryParam.name" class="m-0" :placeholder="t('upload.attachmentCategoryPlaceholder')"
prefix-icon="Search" @input="getAttachmentCategoryList()" />
<div class="group-list flex-1 my-[10px]">
<el-scrollbar>
<div class="group-item p-[10px] leading-none text-xs rounded cursor-pointer"
:class="{ active: attachmentParam.cate_id == 0 }" @click="attachmentParam.cate_id = 0">
{{ t('selectPlaceholder') }}
</div>
<div class="group-item px-[10px] text-xs rounded cursor-pointer flex"
v-for="(item, index) in attachmentCategory.data" :key="index"
:class="{ active: attachmentParam.cate_id == item.id }">
<div class="flex-1 leading-none truncate py-[10px]" @click="attachmentParam.cate_id = item.id">{{
item.name }}
</div>
<div class="leading-none operate py-[10px]" v-if="scene == 'attachment'">
<!-- 编辑分组 -->
<popover-input :placeholder="t('upload.attachmentCategoryPlaceholder')"
@confirm="updateAttachmentCategory($event, index)" :value="item.name">
<span class="text-primary">{{ t('edit') }}</span>
</popover-input>
<!-- 删除分组 -->
<span class="text-danger ml-[5px]" @click="deleteAttachmentCategory(index)">{{ t('delete')
}}</span>
</div>
</div>
</el-scrollbar>
</div>
<!-- 添加分组 -->
<popover-input :placeholder="t('upload.attachmentCategoryPlaceholder')" @confirm="addAttachmentCategory">
<el-button>{{ t('upload.addAttachmentCategory') }}</el-button>
</popover-input>
</div>
<!-- 素材 -->
<div class="attachment-list-wrap flex flex-col p-[15px] flex-1">
<el-row :gutter="15" class="h-[32px]">
<el-col :span="12">
<el-upload v-bind="upload" ref="uploadRef">
<el-button type="primary">{{ t('upload.upload' + type) }}</el-button>
</el-upload>
</el-col>
<el-col :span="12" class="text-right">
<el-input v-model="attachmentParam.real_name" class="m-0 w-[200px]"
:placeholder="t('upload.placeholder' + type + 'Name')" prefix-icon="Search"
@input="getAttachmentList()" />
</el-col>
</el-row>
<div class="flex-1 my-[15px] h-0" v-loading="attachment.loading">
<el-scrollbar>
<div class="flex flex-wrap" v-if="attachment.data.length">
<div class="attachment-item mr-[10px]" :class="scene == 'select' ? 'w-[100px]' : 'w-[120px]'"
v-for="(item, index) in attachment.data" :key="index">
<div class="attachment-wrap w-full rounded cursor-pointer overflow-hidden relative flex items-center justify-center"
:class="scene == 'select' ? 'h-[100px]' : 'h-[120px]'" @click="selectFile(item)">
<el-image :src="img(item.url)" fit="contain" v-if="type == 'image'"></el-image>
<video :src="img(item.url)" v-else></video>
<div class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60"
v-show="selectedFile[item.att_id]">
<icon name="element-Select" color="#fff" size="25px" />
</div>
</div>
<div class="flex items-center">
<el-tooltip placement="top">
<template #content>{{ item.real_name }}</template>
<div class="truncate my-[10px] cursor-pointer text-base flex-1 ">
{{ item.real_name }}
</div>
</el-tooltip>
<!-- 图片操作 -->
<el-dropdown :hide-on-click="false" v-if="scene == 'attachment'">
<icon name="element-MoreFilled" class="cursor-pointer ml-[10px]" size="14px" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item class="text-center" @click="previewImage(index)"
v-if="item.att_type == 'image'">
<div class="text-center w-full">{{ t('lookOver') }}</div>
</el-dropdown-item>
<el-dropdown-item class="text-center" @click="previewVideo(index)"
v-if="item.att_type == 'video'">
<div class="text-center w-full">{{ t('lookOver') }}</div>
</el-dropdown-item>
<el-dropdown-item class="text-center" @click="moveAttachmentEvent(index)">
<div class="text-center w-full">{{ t('upload.move') }}</div>
</el-dropdown-item>
<el-dropdown-item @click="deleteAttachmentEvent(index)">
<div class="text-center w-full">{{ t('delete') }}</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<div class="flex items-center justify-center" v-else>
<el-empty :description="t('upload.attachmentEmpty')" :image-size="100" />
</div>
</el-scrollbar>
</div>
<el-row :gutter="20">
<el-col :span="8" v-if="scene == 'attachment'">
<div class="flex items-center">
<el-checkbox v-model="selectAll" :label="t('selectAll')" size="large" />
<el-button class="ml-[15px]" :disabled="batchOperateDisabled" @click="deleteAttachmentEvent()">{{
t('delete') }}</el-button>
<el-button :disabled="batchOperateDisabled" @click="moveAttachmentEvent()">{{ t('upload.move')
}}</el-button>
</div>
</el-col>
<el-col :span="scene == 'attachment' ? 16 : 24">
<div class="flex h-full justify-end items-center">
<el-pagination v-model:current-page="attachment.page" :small="true"
v-model:page-size="attachment.limit" :page-sizes="[10, 20, 40, 60]"
layout="total, sizes, prev, pager, next, jumper" :total="attachment.total"
@size-change="getAttachmentList()" @current-change="getAttachmentList" />
</div>
</el-col>
</el-row>
</div>
<template v-if="scene == 'attachment'">
<!-- 移动附件分组 -->
<el-dialog v-model="moveAttachmentData.visible" :title="t('upload.moveCategory')" width="350px">
<el-form label-width="60px">
<el-form-item :label="t('upload.moveTo')" style="margin-bottom: 0;">
<el-select v-model="moveAttachmentData.cateId" class="input-width">
<el-option :label="item.name" :value="item.id" v-for="(item, index) in attachmentCategory.data"
:key="index" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="moveAttachmentData.visible = false">{{ t('cancel') }}</el-button>
<el-button type="primary" :loading="moveAttachmentData.loading"
@click="moveAttachmentData.confirm()">{{
t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
<!-- 图片预览 -->
<el-image-viewer :url-list="previewImageList" v-if="imageViewer.show" @close="imageViewer.show = false"
:initial-index="imageViewer.index" :zoom-rate="1" />
<!-- 视频预览 -->
<el-dialog v-model="videoViewer.visible" width="50%" align-center :destroy-on-close="true"
custom-class="video-preview">
<video-player :src="videoViewer.src" width="100%" />
</el-dialog>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, computed, toRaw } from 'vue'
import { t } from '@/lang'
import {
getAttachmentCategoryList as attachmentCategoryList,
getAttachmentList as attachmentList,
addAttachmentCategory as addCategory,
updateAttachmentCategory as updateCategory,
deleteAttachmentCategory as deleteCategory,
deleteAttachment,
moveAttachment
} from '@/api/sys'
import { debounce, img, getToken } from '@/utils/common'
import { ElMessage, UploadFile, UploadFiles, ElMessageBox } from 'element-plus'
import storage from '@/utils/storage'
const prop = defineProps({
//
limit: {
type: Number,
default: 1
},
type: {
type: String,
default: 'image'
},
//
scene: {
type: String,
default: 'select' // select attachment
}
})
//
const selectedFile: Record<string, any> = reactive({})
const attachmentCategory: Record<string, any> = reactive({
data: []
})
const attachment: Record<string, any> = reactive({
loading: false,
page: 1,
total: 0,
limit: prop.scene == 'select' ? 10 : 20,
data: []
})
const categoryParam = reactive({
name: ''
})
const attachmentParam = reactive({
real_name: '',
cate_id: 0
})
/**
* 查询分组
*/
const getAttachmentCategoryList = debounce(() => {
attachmentCategoryList({
type: prop.type,
...categoryParam
}).then(res => {
attachmentCategory.data = res.data
}).catch(() => {
})
})
getAttachmentCategoryList()
/**
* 查询图片
*/
const getAttachmentList = debounce((page: number = 1) => {
attachment.loading = true
attachment.page = page
attachmentList({
page: attachment.page,
limit: attachment.limit,
att_type: prop.type,
...attachmentParam
}).then(res => {
attachment.data = res.data.data
attachment.total = res.data.total
attachment.loading = false
prop.scene == 'attachment' && clearSelected()
}).catch(() => {
attachment.loading = false
})
})
getAttachmentList()
watch(() => attachmentParam.cate_id, () => {
getAttachmentList()
})
/**
* 添加分组
*/
const addAttachmentCategory = (name: string) => {
addCategory({
type: prop.type,
name
}).then(res => {
getAttachmentCategoryList(1)
}).catch(() => {
})
}
/**
* 编辑分组
* @param name
* @param index
*/
const updateAttachmentCategory = (name: string, index: number) => {
updateCategory({
id: attachmentCategory.data[index].id,
name
}).then(res => {
attachmentCategory.data[index].name = name
}).catch(() => {
})
}
/**
* 删除分组
*/
const deleteAttachmentCategory = (index: number) => {
ElMessageBox.confirm(t('upload.deleteCategoryTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteCategory(attachmentCategory.data[index].id).then(() => {
attachmentCategory.data.splice(index, 1)
}).catch(() => {
})
})
}
const uploadRef = ref<Record<string, any> | null>(null)
//
const upload = computed(() => {
const headers: Record<string, any> = {}
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
return {
action: `${import.meta.env.VITE_APP_BASE_URL}/sys/${prop.type}`,
multiple: true,
data: {
cate_id: attachmentParam.cate_id
},
headers,
onSuccess: (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
if (response.code == 200) {
getAttachmentList()
uploadRef.value?.handleRemove(uploadFile)
} else {
uploadFile.status = 'fail'
ElMessage({ message: response.msg, type: 'error' })
}
}
}
})
//
const selectAll = ref(false)
watch(selectAll, () => {
if (selectAll.value) {
const keys = Object.keys(toRaw(selectedFile))
attachment.data.forEach((item: Record<string, any>) => {
if (!keys.includes(item.att_id)) selectedFile[item.att_id] = toRaw(item)
})
} else {
clearSelected()
}
})
/**
* 清空选中
*/
const clearSelected = () => {
const keys = Object.keys(toRaw(selectedFile))
if (keys.length) {
keys.forEach((key) => { delete selectedFile[key] })
selectAll.value = false
}
}
/**
* 选择文件
*/
const selectFile = (data: any) => {
if (selectedFile[data.att_id]) delete selectedFile[data.att_id]
else if (prop.scene == 'select') {
const keys = Object.keys(toRaw(selectedFile))
const length = keys.length
if (prop.limit == 1 && length == prop.limit) {
delete selectedFile[keys[0]]
} else if (length >= prop.limit) {
ElMessage.info(t('upload.triggerUpperLimit'))
return
}
selectedFile[data.att_id] = toRaw(data)
} else {
selectedFile[data.att_id] = toRaw(data)
}
}
/**
* 删除附件
* @param index
*/
const deleteAttachmentEvent = (index: number | null = null) => {
const ids = index === null ? Object.keys(toRaw(selectedFile)) : [attachment.data[index].att_id]
ElMessageBox.confirm(t('upload.deleteAttachmentTips'), t('warning'),
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning'
}
).then(() => {
deleteAttachment({ att_ids: ids }).then(() => {
getAttachmentList()
}).catch(() => {
})
})
}
/**
* 移动附件
*/
const moveAttachmentData: Record<string, any> = reactive({
cateId: '',
loading: false,
visible: false
})
const moveAttachmentEvent = (index: number | null = null) => {
const ids = index === null ? Object.keys(toRaw(selectedFile)) : [attachment.data[index].att_id]
moveAttachmentData.visible = true
moveAttachmentData.cateId = attachmentCategory.data[0].id
moveAttachmentData.confirm = () => {
moveAttachmentData.loading = true
moveAttachment({ cate_id: moveAttachmentData.cateId, att_ids: ids }).then(() => {
moveAttachmentData.visible = false
moveAttachmentData.loading = false
getAttachmentList()
}).catch(() => {
moveAttachmentData.loading = false
})
}
}
//
const batchOperateDisabled = ref(true)
watch(selectedFile, () => {
batchOperateDisabled.value = Object.keys(toRaw(selectedFile)).length == 0
})
/**
* 查看图片
*/
const imageViewer = reactive({
show: false,
index: 0
})
const previewImage = (index: number) => {
imageViewer.show = true
imageViewer.index = index
}
const previewImageList = computed(() => {
return toRaw(attachment.data).map((item: Record<string, any>) => { return img(item.url) })
})
/**
* 视频预览
*/
const videoViewer = reactive({
visible: false,
src: ''
})
const previewVideo = (index: number) => {
videoViewer.visible = true
videoViewer.src = img(attachment.data[index].url)
}
defineExpose({
selectedFile
})
</script>
<style lang="scss" scoped>
.group-list {
.group-item {
margin-top: 3px;
.operate {
display: none;
}
&.active {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
&:hover {
background-color: var(--el-color-primary-light-9);
.operate {
display: block;
}
}
}
}
.attachment-list-wrap {
.attachment-wrap {
background: var(--el-border-color-extra-light);
}
}
</style>
<style lang="scss">
.video-preview {
background: none !important;
box-shadow: none !important;
.el-dialog__headerbtn .el-dialog__close {
border-radius: 50%;
width: 34px;
height: 34px;
font-size: 24px;
color: #fff;
background-color: var(--el-text-color-regular);
border-color: #fff;
}
}
.el-upload-list {
position: absolute !important;
z-index: 10;
.el-upload-list__item {
background: #fff !important;
box-shadow: var(--el-box-shadow-light);
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<span @click="openDialog" class="cursor-pointer">
<slot></slot>
</span>
<el-dialog v-model="showDialog" :title="t('upload.select' + type)" width="60%" class="attachment-dialog"
:destroy-on-close="true">
<attachment :limit="limit" :type="type" ref="attachmentRef" />
<template #footer>
<span class="dialog-footer">
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirm">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import attachment from './attachment.vue'
const prop = defineProps({
//
limit: {
type: Number,
default: 1
},
type: {
type: String,
default: 'image'
}
})
const showDialog = ref(false)
const attachmentRef: Record<string, any> | null = ref(null)
const openDialog = () => {
showDialog.value = true
}
const emit = defineEmits(['confirm'])
/**
* 确认选择
*/
const confirm = () => {
showDialog.value = false
const files = Object.values(attachmentRef?.value.selectedFile)
emit('confirm', prop.limit == 1 ? files[0] ?? null : files)
}
defineExpose({
showDialog
})
</script>
<style lang="scss">
.attachment-dialog {
.el-dialog__body {
padding: 0 !important;
}
.el-upload-list {
position: absolute;
z-index: 5;
}
.el-upload-list__item {
background: var(--el-dialog-bg-color);
box-shadow: var(--el-dialog-box-shadow);
}
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<el-upload v-bind="upload" class="w-full upload-file">
<slot>
<el-input v-model="value" class="w-full" :readonly="true">
<template #append>{{ t('upload.root') }}</template>
</el-input>
</slot>
</el-upload>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { t } from '@/lang'
import { getToken } from '@/utils/common'
import { UploadFile, ElMessage } from 'element-plus'
import storage from '@/utils/storage'
const prop = defineProps({
modelValue: {
type: String,
default: ''
},
api: {
type: String,
default: 'sys/document'
}
})
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const upload: Record<string, any> = {
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
showFileList: false,
headers: {},
accept: '.doc,.docx,.xml,.txt,.pem,.zip,.rar,.7z',
onSuccess: (response: any, uploadFile: UploadFile) => {
value.value = response.data.url
ElMessage({
message: t('upload.success'),
type: 'success'
})
}
}
upload.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
upload.headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
</script>
<style lang="scss">
.upload-file .el-upload {
width: 100%;
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<div class="flex flex-wrap">
<template v-if="limit == 1">
<div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color image-wrap mr-[10px]"
:style="style">
<div class="w-full h-full relative" v-if="images.data.length">
<div class="w-full h-full flex items-center justify-center">
<el-image :src="img(images.data[0])" fit="contain"></el-image>
</div>
<div
class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
<icon name="element-ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage()" />
<icon name="element-Delete" color="#fff" size="18px" @click="removeImage" />
</div>
</div>
<upload-attachment :limit="limit" @confirm="confirmSelect" v-else>
<div class="w-full h-full flex items-center justify-center flex-col">
<icon name="element-Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
</div>
</upload-attachment>
</div>
</template>
<template v-else>
<div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color image-wrap mr-[10px]"
:style="style" v-for="(item, index) in images.data" :key="index">
<div class="w-full h-full relative">
<div class="w-full h-full flex items-center justify-center">
<el-image :src="img(item)" fit="contain"></el-image>
</div>
<div
class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
<icon name="element-ZoomIn" color="#fff" size="18px" class="mr-[10px]"
@click="previewImage(index)" />
<icon name="element-Delete" color="#fff" size="18px" @click="removeImage(index)" />
</div>
</div>
</div>
<div class="rounded cursor-pointer overflow-hidden relative border border-dashed border-color" :style="style"
v-if="images.data.length < limit">
<upload-attachment :limit="limit" @confirm="confirmSelect">
<div class="w-full h-full flex items-center justify-center flex-col">
<icon name="element-Plus" size="20px" color="var(--el-text-color-secondary)" />
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText || t('upload.root') }}</div>
</div>
</upload-attachment>
</div>
</template>
</div>
<el-image-viewer :url-list="previewImageList" v-if="imageViewer.show" @close="imageViewer.show = false"
:initial-index="imageViewer.index" :zoom-rate="1" />
</template>
<script lang="ts" setup>
import { computed, reactive, watch, toRaw } from 'vue'
import { img } from '@/utils/common'
import { t } from '@/lang'
const prop = defineProps({
modelValue: {
type: String,
default: ''
},
width: {
type: String,
default: '100px'
},
height: {
type: String,
default: '100px'
},
imageText: {
type: String
},
limit: {
type: Number,
default: 1
}
})
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const images: Record<string, any> = reactive({
data: []
})
let previewImageList: string[] = reactive([])
const setValue = () => {
value.value = toRaw(images.data).toString()
previewImageList = toRaw(images.data).map((url: string) => { return img(url) })
}
watch(() => value.value, () => {
images.data = [
...value.value.split(',').filter((item: string) => { return item })
]
setValue()
}, { immediate: true })
const style = computed(() => {
return {
width: prop.width,
height: prop.height
}
})
/**
* 选择图片
*/
const confirmSelect = (data: Record<string, any>) => {
if (prop.limit == 1) {
images.data.splice(0, 1)
data && images.data.push(data.url)
} else {
data.forEach((item: any) => {
if (images.data.length < prop.limit) images.data.push(item.url)
})
}
setValue()
}
/**
* 删除图片
* @param index
*/
const removeImage = (index: number = 0) => {
images.data.splice(index, 1)
setValue()
}
/**
* 查看图片
*/
const imageViewer = reactive({
show: false,
index: 0
})
const previewImage = (index: number = 0) => {
imageViewer.show = true
imageViewer.index = index
}
</script>
<style lang="scss" scoped>
.image-wrap {
.operation {
display: none;
}
&:hover {
.operation {
display: flex;
}
}
}
</style>

View File

@ -0,0 +1,163 @@
<template>
<div class="flex flex-wrap">
<template v-if="limit == 1">
<div class="rounded cursor-pointer relative bg-page video-wrap mr-[10px]" :style="style">
<template v-if="videos.data.length">
<div class="w-full h-full relative flex items-center overflow-hidden rounded">
<video :src="img(videos.data[0])" class="w-full" />
<div
class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
<icon name="iconfont-icon24gf-playCircle" color="#fff" size="25px" @click="previewVideo()" />
</div>
</div>
<icon name="element-CircleCloseFilled" color="#bbb" size="18px" @click="removeVideo"
class="absolute z-[2] top-[-9px] right-[-9px]" />
</template>
<upload-attachment :limit="limit" type="video" @confirm="confirmSelect" v-else>
<div class="w-full h-full flex items-center justify-center flex-col">
<icon name="iconfont-icon24gf-playCircle" size="25px" color="var(--el-text-color-secondary)" />
</div>
</upload-attachment>
</div>
</template>
<template v-else>
<div class="rounded cursor-pointer relative bg-page video-wrap mr-[10px]" :style="style"
v-for="(item, index) in videos.data" :key="index">
<div class="w-full h-full relative flex items-center overflow-hidden rounded">
<video :src="img(item)" class="w-full" />
<div
class="absolute z-[1] flex items-center justify-center w-full h-full inset-0 bg-black bg-opacity-60 operation">
<icon name="iconfont-icon24gf-playCircle" color="#fff" size="25px" @click="previewVideo(index)" />
</div>
</div>
<icon name="element-CircleCloseFilled" color="#bbb" size="18px" @click="removeVideo(index)"
class="absolute z-[2] top-[-9px] right-[-9px]" />
</div>
<div class="rounded cursor-pointer relative bg-page video-wrap mr-[10px]" :style="style"
v-if="videos.data.length < limit">
<upload-attachment :limit="limit" type="video" @confirm="confirmSelect">
<div class="w-full h-full flex items-center justify-center flex-col">
<icon name="iconfont-icon24gf-playCircle" size="25px" color="var(--el-text-color-secondary)" />
</div>
</upload-attachment>
</div>
</template>
<!-- 视频预览 -->
<el-dialog v-model="videoViewer.visible" width="50%" align-center :destroy-on-close="true"
custom-class="video-preview">
<video-player :src="videoViewer.src" width="100%" />
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, watch, toRaw } from 'vue'
import { img } from '@/utils/common'
const prop = defineProps({
modelValue: {
type: String,
default: ''
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '100px'
},
limit: {
type: Number,
default: 1
}
})
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return prop.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const videos: Record<string, any> = reactive({
data: []
})
watch(() => value.value, () => {
videos.data = [
...value.value.split(',').filter((item: string) => { return item })
]
setValue()
})
const style = computed(() => {
return {
width: prop.width,
height: prop.height
}
})
/**
* 选择视频
*/
const confirmSelect = (data: Record<string, any>) => {
if (prop.limit == 1) {
videos.data.splice(0, 1)
data && videos.data.push(data.url)
} else {
data.forEach((item: any) => {
if (videos.data.length < prop.limit) videos.data.push(item.url)
})
}
setValue()
}
/**
* 删除视频
* @param index
*/
const removeVideo = (index: number = 0) => {
videos.data.splice(index, 1)
setValue()
}
const setValue = () => {
value.value = toRaw(videos.data).toString()
}
/**
* 查看视频
*/
const videoViewer = reactive({
visible: false,
src: ''
})
const previewVideo = (index: number = 0) => {
videoViewer.visible = true
videoViewer.src = img(videos.data[index])
}
</script>
<style lang="scss">
.video-preview {
background: none !important;
box-shadow: none !important;
.el-dialog__headerbtn .el-dialog__close {
border-radius: 50%;
width: 34px;
height: 34px;
font-size: 24px;
color: #fff;
background-color: var(--el-text-color-regular);
border-color: #fff;
}
}
</style>

View File

@ -0,0 +1,10 @@
<template>
<video-play />
</template>
<script lang="ts" setup>
import 'vue3-video-play/dist/style.css'
import videoPlay from 'vue3-video-play'
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,3 @@
{
"tips": "Your account permissions are insufficient. Please contact the administrator to add permissions!"
}

View File

@ -0,0 +1,21 @@
{
"menuName": "Menu name",
"menuType": "Type",
"authId": "Permission ID",
"menuTypeDir": "Dir",
"menuTypeMenu": "Menu",
"menuTypeButton": "Button",
"menuDeleteTips": "Are you sure you want to delete this menu?",
"addMenu": "Add menu",
"updateMenu": "Update menu",
"routePath": "Route path",
"viewPath": "Component path",
"parentMenu": "Parent menu",
"menuIcon": "Menu Icon",
"menuNamePlaceholder": "Please enter a menu name",
"routePathPlaceholder": "Please enter routing path",
"viewPathPlaceholder": "Please enter the component path",
"authIdPlaceholder": "Please enter Permission id",
"selectIconPlaceholder": "Please select the menu icon",
"topLevel": "top-level"
}

View File

@ -0,0 +1,38 @@
{
"edit": "Edit",
"delete": "Delete",
"createTime": "Create Time",
"sort": "Sort",
"status": "Status",
"operation": "Operation",
"statusNormal": "Normal",
"statusDeactivate": "Deactivate",
"confirm": "Confirm",
"cancel": "Cancel",
"warning": "Warning",
"isShow": "Show or not",
"show": "show",
"hidden": "hidden",
"icon": "Icon",
"layout": {
"layoutSetting": "Layout configuration",
"darkMode": "Dark mode",
"themeColor": "Theme color"
},
"axios": {
"unknownError": "Unknown Error",
"400": "Wrong request ",
"401": "Please login again",
"403": "Access denied",
"404": "Request error, the resource was not found",
"405": "Request method not allowed",
"408": "Request timeout",
"500": "Server side error",
"501": "Network not implemented",
"502": "Network error",
"503": "Service unavailable",
"504": "Network Timeout",
"505": "The http version does not support the request",
"timeout": "Network request timeout!"
}
}

View File

@ -0,0 +1,25 @@
{
"todayData": "today's data",
"memberNumb": "number of member",
"numberOfSites": "number of sites",
"numberOfVisitors": "number of visitors",
"commonlyUsedFunction": "commonly used function",
"articleList": "article list",
"memberManagement": "member management",
"balanceAccount": "balance account",
"administrator": "administrator",
"WebDecoration": "website decoration",
"accessMessage": "access message",
"memberDistribution": "membership distribution",
"systemInfo": "system environment",
"os": "os",
"phpVersions": "php version number",
"productionEnvironment": "production environment",
"versionsInfo": "version information",
"versions": "current version",
"frame": "framework based",
"channel": "access channel",
"serviceSupport": "service support",
"officialWbsite": "official website",
"pageView": "page view"
}

View File

@ -0,0 +1,7 @@
{
"title": "Niushop Vite Management side",
"login": "Login",
"logging": "Logging",
"userPlaceholder": "Please enter your account number",
"passwordPlaceholder": "Please enter your password"
}

View File

@ -0,0 +1,26 @@
{
"channel":"logon mode",
"nickname":"Name of member",
"mobile":"mobile",
"createTime":"creation time",
"updateTime":"Update time",
"addMember":"add member",
"updateMember":"edit member",
"nickNamePlaceholder":"Please enter the member name",
"mobilePlaceholder":"Please enter your mobile phone number",
"channelPlaceholder":"Please select a registration type",
"memberDeleteTips" : "Are you sure you want to delete this member?",
"detail": "detail",
"edit": "edit",
"startDate": "start time",
"endDate": "end time",
"essentialInfo": "essential information",
"accountInfo": "account information",
"registeredSource": "registered source",
"memberLabel": "Member label",
"urserName": "user name",
"point": "point",
"balance": "balance",
"growth": "growth"
}

View File

@ -0,0 +1,14 @@
{
"headImg": "head",
"realName": "name",
"originalPassword": "original password",
"password": "new password",
"passwordCopy": "confirm password",
"passwordTip": "This parameter is mandatory when changing the password. Leave a blank if you do not change the password",
"realNamePlaceholder": "Please enter the user name",
"headImgPlaceholder": "Please enter the user profile picture",
"originalPasswordPlaceholder": "Please enter the original password",
"passwordPlaceholder": "Please enter a new password",
"save": "save",
"cancel": "cancel"
}

21
admin/src/lang/i18n.ts Normal file
View File

@ -0,0 +1,21 @@
import { createI18n } from "vue-i18n"
import Language from "./language"
import zhCn from "./zh-cn/common.json";
import en from "./en/common.json"
//创建实例
let i18n = createI18n({
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
messages: {
"zh-cn": zhCn,
en
}
});
const language = new Language(i18n);
export { language };
export default i18n;

20
admin/src/lang/index.ts Normal file
View File

@ -0,0 +1,20 @@
import i18n, { language } from "./i18n"
import useAppStore from '@/stores/modules/app'
const t = (message: string) => {
const path = useAppStore().route
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
const key = `${file}.${message}`
return i18n.global.t(key) != key ? i18n.global.t(key) : i18n.global.t(message)
}
export { language, t }
export default {
install(app: any) {
//注册i18n
app.use(i18n);
}
};

View File

@ -0,0 +1,58 @@
import { nextTick } from 'vue'
class Language {
private i18n: any;
private loadLocale: Array<string> = []; //已加载的语言
constructor(i18n: any) {
this.i18n = i18n
}
/**
*
* @param locale
*/
public setI18nLanguage(locale: string) {
if (this.i18n.mode === 'legacy') {
this.i18n.global.locale = locale
} else {
this.i18n.global.locale = locale
}
let html = document.querySelector('html')
html && html.setAttribute('lang', locale)
}
/**
*
* @param path
* @param locale
* @returns
*/
public async loadLocaleMessages(path: string, locale: string) {
try {
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
if (this.loadLocale.includes(`${locale}/${file}`)) {
return nextTick()
}
this.loadLocale.push(`${locale}/${file}`)
// 引入语言包文件
const messages = await import(`./${locale}/${file}.json`)
let data: Record<string, string> = {}
Object.keys(messages.default).forEach(key => {
data[`${file}.${key}`] = messages.default[key]
})
this.i18n.global.mergeLocaleMessage(locale, data)
this.setI18nLanguage(locale)
return nextTick()
} catch {
this.setI18nLanguage(locale)
return nextTick()
}
}
}
export default Language

View File

@ -0,0 +1,3 @@
{
"tips": "您的账号权限不足,请联系管理员添加权限!"
}

View File

@ -0,0 +1,18 @@
{
"siteId":"站点ID",
"name":"栏目名称",
"sort":"排序",
"isShow":"是否显示",
"siteIdPlaceholder":"请输入站点ID",
"namePlaceholder":"请输入栏目名称",
"sortPlaceholder":"请输入排序",
"isShowPlaceholder":"是否显示",
"addArticleCategory":"添加栏目",
"updateArticleCategory":"编辑栏目",
"articleCategoryDeleteTips" : "确定要删除该栏目吗?",
"nameMax":"名称不能超过20个字符",
"sortNumber":"排序号必须是数字",
"sortBetween":"排序号不能超过10000",
"show":"显示",
"hide":"不显示"
}

View File

@ -0,0 +1,35 @@
{
"categoryName":"文章栏目",
"title":"文章标题",
"intro":"简介",
"summary":"文章摘要",
"image":"文章图片",
"author":"作者",
"content":"文章内容",
"visit":"实际浏览量",
"visitVirtual":"虚拟浏览量",
"isShow":"是否显示",
"sort":"排序",
"categoryIdPlaceholder":"请选择文章栏目",
"titlePlaceholder":"请输入文章标题",
"introPlaceholder":"请输入简介",
"summaryPlaceholder":"请输入文章摘要",
"imagePlaceholder":"请上传文章图片",
"authorPlaceholder":"请输入作者",
"contentPlaceholder":"请输入文章内容",
"visitPlaceholder":"请输入实际浏览量",
"visitVirtualPlaceholder":"请输入虚拟浏览量",
"isShowPlaceholder":"是否显示",
"sortPlaceholder":"请输入排序",
"addArticle":"添加文章",
"updateArticle":"编辑文章",
"titleMax":"文章标题不能超过20个字符",
"introMax":"文章简介不能超过50个字符",
"summaryMax":"文章摘要不能超过50个字符",
"imageMax":"图片路径太长",
"authorMax":"文章作者不能超过20个字符",
"isShowNumber":"是否显示必须是数字",
"isShowBetween":"是否显示只能是0或者1",
"sortNumber":"排序号必须是数字",
"sortBetween":"排序号需要在0-10000之间"
}

View File

@ -0,0 +1,20 @@
{
"categoryName":"栏目",
"title":"标题",
"intro":"简介",
"summary":"摘要",
"image":"封面",
"author":"作者",
"content":"文章内容",
"visit":"实际浏览量",
"visitVirtual":"虚拟浏览量",
"isShow":"是否显示",
"sort":"排序",
"createTime":"创建时间",
"updateTime":"更新时间",
"addArticle":"添加文章",
"updateArticle":"编辑文章",
"titlePlaceholder":"请输入文章标题",
"categoryIdPlaceholder":"请选择文章栏目",
"articleDeleteTips" : "确定要删除该文章吗?"
}

View File

@ -0,0 +1,19 @@
{
"ip":"登录IP",
"username":"管理员姓名",
"url":"链接",
"detail": "详情",
"params":"参数",
"type":"请求方式",
"createTime":"操作时间",
"ipPlaceholder":"请输入登录IP",
"uidPlaceholder":"请输入管理员id",
"usernamePlaceholder":"请输入管理员姓名",
"urlPlaceholder":"请输入链接",
"paramsPlaceholder":"请输入参数",
"typePlaceholder":"请输入请求方式",
"createTimePlaceholder":"请输入操作时间",
"addSysUserLog":"添加管理员操作记录表",
"updateSysUserLog":"编辑管理员操作记录表",
"sys_user_logDeleteTips":"确定要删除该管理员操作记录表吗?"
}

View File

@ -0,0 +1,25 @@
{
"menuName": "菜单名称",
"menuType": "类型",
"authId": "权限标识",
"menuTypeDir": "目录",
"menuTypeMenu": "菜单",
"menuTypeButton": "按钮",
"menuDeleteTips": "确定要删除该菜单吗?",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",
"routePath": "路由路径",
"viewPath": "组件路径",
"parentMenu": "父级菜单",
"menuIcon": "菜单图标",
"sort":"权重",
"menuKey":"菜单标识",
"menuNamePlaceholder": "请输入菜单名称",
"menuKeyPlaceholder": "请输入菜单标识",
"menuKeyValidata":"菜单标识只能使用字母数字下划线并且开头不能为数字",
"routePathPlaceholder": "请输入路由路径",
"viewPathPlaceholder": "请输入组件路径",
"authIdPlaceholder": "请输入权限标识",
"selectIconPlaceholder": "请选择菜单图标",
"topLevel": "顶级"
}

View File

@ -0,0 +1,25 @@
{
"menuName": "菜单名称",
"menuType": "类型",
"authId": "权限标识",
"menuTypeDir": "目录",
"menuTypeMenu": "菜单",
"menuTypeButton": "按钮",
"menuDeleteTips": "确定要删除该菜单吗?",
"addMenu": "添加菜单",
"updateMenu": "编辑菜单",
"routePath": "路由路径",
"viewPath": "组件路径",
"parentMenu": "父级菜单",
"menuIcon": "菜单图标",
"sort":"权重",
"menuKey":"菜单标识",
"menuNamePlaceholder": "请输入菜单名称",
"menuKeyPlaceholder": "请输入菜单标识",
"menuKeyValidata":"菜单标识只能使用字母数字下划线并且开头不能为数字",
"routePathPlaceholder": "请输入路由路径",
"viewPathPlaceholder": "请输入组件路径",
"authIdPlaceholder": "请输入权限标识",
"selectIconPlaceholder": "请选择菜单图标",
"topLevel": "顶级"
}

View File

@ -0,0 +1,10 @@
{
"addRole": "新增角色",
"updateRole": "编辑角色",
"roleName": "角色名称",
"roleDeleteTips": "确定要删除该角色吗?",
"roleNamePlaceholder": "请输入角色名称",
"rulesPlaceholder": "请选择权限",
"checkStrictly": "父子级不关联",
"permission": "权限"
}

View File

@ -0,0 +1,20 @@
{
"addUser": "新增管理员",
"updateUser": "编辑管理员",
"userRealName": "姓名",
"lastLoginTime": "最后登录时间",
"lastLoginIP": "最后登录IP",
"accountNumberPlaceholder": "请输入账号",
"passwordPlaceholder": "请输入密码",
"confirmPasswordPlaceholder": "请再次确认密码",
"userRealNamePlaceholder": "请输入姓名",
"confirmPasswordError": "两次输入的密码不一致",
"userRoleName": "角色",
"administrator":"超级管理员",
"userRolePlaceholder": "请选择角色",
"lock":"锁定",
"unlock":"解锁",
"status":"用户状态",
"userUnlockTips":"确定要解锁该管理员吗?",
"userLockTips":"确定要锁定该管理员吗?"
}

View File

@ -0,0 +1,34 @@
{
"aliappSet": "小程序设置",
"aliappName": "小程序名称",
"aliappOriginal": "APPID",
"aliappQrcode": "小程序二维码",
"aliappQrcodeTips": "建议尺寸上传430px*430px宽高的二维码",
"aliappAppid": "应用私钥",
"aliappAppsecret": "开发者密码",
"downloadUrl": "downloadFile合法域名",
"aliappInfo": "小程序信息",
"aliappDevelopInfo": "开发设置",
"theServerSetting": "接口内容加密设置",
"functionSetting": "服务器配置信息",
"encryptionType": "消息加解密方式",
"compatibleMode": "兼容模式",
"safeMode": "安全模式(推荐)",
"aliappNamePlaceholder": "请输入小程序名称",
"aliappOriginalPlaceholder": "请输入APPID",
"appidPlaceholder": "请输入应用私钥",
"tokenPlaceholder": "请输入Token",
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"countersignType": "接口加签方式",
"certificate": "证书",
"publicKey": "应用公钥证书",
"alipayPublicKey": "支付宝公钥证书",
"alipayWithCrt": "支付宝根证书",
"publicKeyTips": "上传appCertPublicKey_***.crt文件",
"alipayPublicKeyTips": "上传alipayCertPublicKey_RSA2.crt文件",
"alipayWithCrtTips": "上传alipayRootCert.crt文件",
"AESKey": "AES秘钥",
"serveWhiteList": "服务器域名白名单",
"AESKeyPlaceholder": "请输入AES秘钥"
}

View File

@ -0,0 +1,6 @@
{
"isOpen": "是否开启",
"h5DomainName": "h5域名",
"H5Info": "h5配置信息"
}

View File

@ -0,0 +1,4 @@
{
"pcInfo": "电脑端信息",
"preview": "预览图"
}

View File

@ -0,0 +1,34 @@
{
"weappName": "小程序名称",
"weappOriginal": "小程序原始ID",
"weappQrcode": "小程序二维码",
"weappQrcodeTips": "建议尺寸上传430px*430px宽高的二维码",
"weappAppid": "开发者ID",
"weappAppidTips": "开发者ID是小程序开发识别码配合开发者密码可调用小程序的接口能力。",
"weappAppsecret": "开发者密码",
"weappAppsecretTips": "开发者密码是校验小程序开发者身份的密码,具有极高的安全性",
"businessDomain": "业务域名",
"requestUrl": "request合法域名",
"socketUrl": "socket合法域名",
"uploadUrl": "uploadFile合法域名",
"downloadUrl": "downloadFile合法域名",
"weappInfo": "小程序信息",
"weappDevelopInfo": "小程序开发信息",
"theServerSetting": "消息推送配置",
"functionSetting": "服务器域名设置",
"tokenTips": "必须为英文或数字长度为3-32字符。",
"encodingAESKeyTips": "消息加密密钥由43位字符组成可随机修改字符范围为A-Za-z0-9。",
"encryptionType": "消息加解密方式",
"cleartextMode": "明文模式",
"compatibleMode": "兼容模式",
"safeMode": "安全模式(推荐)",
"weappNamePlaceholder": "请输入小程序名称",
"weappOriginalPlaceholder": "请输入小程序原始ID",
"appidPlaceholder": "请输入开发者ID",
"appSecretPlaceholder": "请输入开发者密码",
"tokenPlaceholder": "请输入Token",
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
}

View File

@ -0,0 +1,19 @@
{
"name": "类型",
"response": "回复内容",
"isStart": "是否启用",
"serialNumber": "编号",
"operation": "操作",
"operationTip": "温馨提示",
"operationTipOne": "请在小程序的服务类目中添加类目:一级类目:商业服务 二级类目:软件/建站/技术开发",
"operationTipTwo": "小程序最多支持50个消息模板获取时请注意小程序剩余模板数量是否充足",
"buyerNews": "买家消息",
"sellerMessage": "卖家消息",
"started": "启用",
"closed": "禁用",
"close": "关闭",
"open": "开启",
"regain": "重新获取",
"batchAcquisition": "一键获取"
}

View File

@ -0,0 +1,33 @@
{
"wechatName": "公众号名称",
"wechatOriginal": "公众号原始ID",
"wechatQrcode": "公众号二维码",
"wechatQrcodeTips": "建议尺寸上传430px*430px宽高的二维码",
"wechatAppid": "开发者ID",
"wechatAppidTips": "开发者ID是公众号开发识别码配合开发者密码可调用公众号的接口能力。",
"wechatAppsecret": "开发者密码",
"wechatAppsecretTips": "开发者密码是校验公众号开发者身份的密码,具有极高的安全性",
"businessDomain": "业务域名",
"jsSecureDomain": "JS接口安全域名",
"webAuthDomain": "网页授权域名",
"wechatInfo": "公众号信息",
"wechatDevelopInfo": "公众号开发信息",
"theServerSetting": "消息推送配置",
"functionSetting": "功能设置",
"functionSettingTips" : "在微信公众平台中,点击 设置与开发>公众号设置>功能设置,设置业务域名 JS接口安全域名 网页授权域名",
"tokenTips": "必须为英文或数字长度为3-32字符。",
"encodingAESKeyTips": "消息加密密钥由43位字符组成可随机修改字符范围为A-Za-z0-9。",
"encryptionType": "消息加解密方式",
"cleartextMode": "明文模式",
"compatibleMode": "兼容模式",
"safeMode": "安全模式(推荐)",
"wechatNamePlaceholder": "请输入公众号名称",
"wechatOriginalPlaceholder": "请输入公众号原始ID",
"appidPlaceholder": "请输入开发者ID",
"appSecretPlaceholder": "请输入开发者密码",
"tokenPlaceholder": "请输入Token",
"encodingAesKeyPlaceholder": "请输入EncodingAESKey",
"cleartextModeTips": "明文模式下,不使用消息体加解密功能,安全系数较低",
"compatibleModeTips": "兼容模式下,明文、密文将共存,方便开发者调试和维护",
"safeModeTips": "安全模式下,消息包为纯密文,需要开发者加密和解密,安全系数高"
}

View File

@ -0,0 +1,20 @@
{
"menuNameInfo": "菜单信息",
"subMenuNameInfo": "子菜单信息",
"menuNameTips": "仅支持中英文和数字字数不超过4个汉字或8个字母。",
"subMenuNameTips": "仅支持中英文和数字字数不超过8个汉字或16个字母。",
"menuName": "名称",
"deleteMemu": "删除菜单",
"deleteMemuTips": "删除后,菜单下设置的内容全部将被删除",
"messageType": "消息类型",
"skipWebpage": "跳转网页",
"skipWeapp": "跳转小程序",
"webpageUrl": "网页链接",
"weappAppid": "小程序Appid",
"weappPage": "小程序页面路径",
"menuNamePlaceholder": "请输入菜单名称",
"webpageUrlPlaceholder": "请输入网页链接",
"weappAppidPlaceholder": "请输入小程序Appid",
"weappPagePlaceholder": "请输入小程序页面路径",
"menusEmptyTips": "空菜单,不能保存与发布。"
}

View File

@ -0,0 +1,23 @@
{
"name": "类型",
"messageType": "消息类型",
"isStart": "是否启用",
"serialNumber": "编号",
"operation": "操作",
"operationTip": "温馨提示",
"operationTipOne": "请将公众平台模板消息所在行业选择为:消费品/消费品,其他/其他,所选行业不一致将会导致模板消息不可用。",
"operationTipTwo": "公众平台模板消息所在行业选择一个月只能修改一次,请谨慎选择。",
"operationTipThree": "公众号最多支持25个模板消息获取时请注意公众号剩余模板数量是否充足",
"operationTipFour": "所需跳转到的小程序必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏",
"buyerNews": "买家消息",
"sellerMessage": "卖家消息",
"started": "启用",
"closed": "禁用",
"close": "关闭",
"open": "开启",
"response": "回复内容",
"regain": "重新获取",
"batchAcquisition": "一键获取",
"id": "id"
}

View File

@ -0,0 +1,100 @@
{
"edit": "编辑",
"delete": "删除",
"info": "详情",
"createTime": "创建时间",
"sort": "排序",
"status": "状态",
"operation": "操作",
"more": "更多",
"statusNormal": "正常",
"statusDeactivate": "停用",
"startUsing": "启用",
"confirm": "确认",
"save": "保存",
"back": "返回",
"cancel": "取消",
"search": "搜索",
"reset": "重置",
"warning": "提示",
"isShow": "是否显示",
"show": "显示",
"hidden": "隐藏",
"icon": "图标",
"userName": "用户名",
"headImg": "头像",
"accountNumber": "账号",
"password": "密码",
"confirmPassword": "确认密码",
"image": "图片",
"video": "视频",
"rename": "重命名",
"lookOver": "查看",
"selectAll": "全选",
"yes": "是",
"no": "否",
"copy": "复制",
"copySuccess": "复制成功",
"notSupportCopy": "浏览器不支持一键复制,请手动进行复制",
"selectPlaceholder": "全部",
"provincePlaceholder": "请选择省",
"cityPlaceholder": "请选择市",
"districtPlaceholder": "请选择区/县",
"emptyData": "暂无数据",
"upload": {
"root": "上传",
"selectimage": "选择图片",
"selectvideo": "选择视频",
"uploadimage": "上传图片",
"uploadvideo": "上传视频",
"addAttachmentCategory": "添加分组",
"attachmentCategoryPlaceholder": "请输入分组名称",
"attachmentEmpty": "暂无附件,请点击上传按钮上传",
"deleteCategoryTips": "确定要删除该分组吗?",
"deleteAttachmentTips": "确定要删除所选附件吗?如所选附件已被使用删除将会受到影响,请谨慎操作!",
"move": "移动",
"moveCategory": "移动分组",
"moveTo": "移动至",
"placeholderimageName": "请输入图片名称",
"placeholdervideoName": "请输入视频名称",
"success": "上传成功",
"triggerUpperLimit": "可选数量已达上限"
},
"tabs": {
"closeLeft": "关闭左侧",
"closeRight": "关闭右侧",
"closeOther": "关闭其他"
},
"layout": {
"layoutSetting": "主题设置",
"darkMode": "黑暗模式",
"sidebarMode": "主题风格",
"themeColor": "主题颜色"
},
"axios": {
"unknownError": "未知错误",
"400": "错误的请求",
"401": "请重新登录",
"403": "拒绝访问",
"404": "请求错误,未找到该资源",
"405": "请求方法未允许",
"408": "请求超时",
"500": "服务器端出错",
"501": "网络未实现",
"502": "网络错误",
"503": "服务不可用",
"504": "网络超时",
"505": "http版本不支持该请求",
"timeout": "网络请求超时!"
},
"linkPlaceholder": "请选择跳转链接",
"selectLinkTips": "链接选择",
"diyLinkName": "链接名称",
"diyLinkNamePlaceholder": "请输入链接名称",
"diyLinkNameNotEmpty": "链接名称不能为空",
"diyLinkUrl": "跳转路径",
"diyLinkUrlPlaceholder": "请输入跳转路径",
"diyLinkUrlNotEmpty": "跳转路径不能为空",
"returnToPreviousPage": "返回",
"preview": "预览"
}

View File

@ -0,0 +1,88 @@
{
"pageSet": "页面设置",
"pageContent": "页面内容",
"pageName": "页面名称",
"pageNamePlaceholder": "请输入页面名称",
"pageBgColor": "页面颜色",
"warmPrompt": "温馨提示",
"leavePageTitleTips": "确定离开此页面?",
"leavePageContentTips": "系统可能不会保存您所做的更改。",
"decorating": "正在装修",
"moveUpComponent": "上移",
"moveDownComponent": "下移",
"copyComponent": "复制",
"delComponent": "删除",
"resetComponent": "重置",
"tabbar": "底部导航",
"tabbarSwitchTips": "此处控制当前页面底部导航菜单是否显示",
"link": "链接地址",
"delComponentTips": "确定要删除吗?",
"notCopy": "无法复制",
"componentCanOnlyAdd": "组件只能添加",
"piece": "个",
"resetComponentTips": "确认要重置组件默认数据吗?",
"image": "图片上传",
"imageUpload": "图片上传",
"imageSet": "图片设置",
"imageAdsTips": "建议上传尺寸相同的图片推荐尺寸750*350",
"addImageAd": "添加图片",
"imageUrlTip": "请上传图片",
"articleData": "文章数据",
"dataSources": "数据来源",
"defaultSources": "默认",
"manualSelectionSources": "手动选择",
"articleNum": "文章数量",
"selectPlaceholder": "请选择",
"selectArticleTips": "文章选择",
"articleTitle": "标题",
"articleImage": "封面",
"articleCategoryName": "栏目",
"articleSummary": "摘要",
"selected": "已选",
"selectArticleTip": "请选择文章",
"graphicNavModeTitle": "导航模式",
"layoutMode": "排版模式",
"layoutModeHorizontal": "横排",
"layoutModeVertical": "竖排",
"graphicNavSelectMode": "选择模式",
"graphicNavModeGraphic": "图文导航",
"graphicNavModeImg": "图片导航",
"graphicNavModeText": "文字导航",
"graphicNavImageSet": "图片设置",
"graphicNavImageSize": "图片大小",
"graphicNavAroundRadius": "图片圆角",
"graphicNavShowStyle": "展示风格",
"graphicNavStyleFixed": "固定显示",
"graphicNavStyleSingleSlide": "单行滑动",
"graphicNavStylePageSlide": "分页滑动",
"graphicNavRowCount": "每行数量",
"graphicNavPageCount": "每行数量",
"graphicNavSetLabel": "导航设置",
"line": "行",
"graphicNavTips": "建议上传尺寸相同的图片推荐尺寸60*60",
"graphicNavTitle": "标题",
"graphicNavTitlePlaceholder": "请输入标题",
"addGraphicNav": "添加导航",
"blankHeightSet": "高度设置",
"blankHeight": "空白高度",
"titleStyle": "标题样式",
"selectStyle": "风格选择",
"titleContent": "标题内容",
"title": "标题名称",
"titlePlaceholder": "请输入标题",
"textAlign": "对齐方式",
"textAlignLeft": "居左",
"textAlignCenter": "居中",
"textFontSize": "文字大小",
"textFontWeight": "文字粗细",
"fontWeightBold": "加粗",
"fontWeightNormal": "常规",
"textColor": "文字颜色",
"subTitleContent": "标题内容",
"subTitle": "副标题",
"subTitlePlaceholder": "请输入副标题",
"moreContent": "“更多”按钮内容",
"more": "文字",
"morePlaceholder": "请输入文字",
"moreIsShow": "是否显示"
}

View File

@ -0,0 +1,10 @@
{
"decorate": "装修",
"preview": "预览",
"weapp": "微信小程序",
"wechat": "微信公众号",
"link": "链接",
"copy": "复制",
"copySuccess": "复制成功",
"weappNotSet": "小程序未配置"
}

View File

@ -0,0 +1,34 @@
{
"title": "页面名称",
"typeName": "页面类型",
"addPageTips": "创建新页面",
"pageTypePlaceholder": "请选择页面类型",
"nameMax": "名称不能超过12个字符",
"status": "状态",
"updateTime": "更新时间",
"use": "使用",
"isUse": "使用中",
"unused": "未使用",
"all": "全部",
"basicRoute": "基础页面",
"diyPage": "自定义页面",
"wapUrl": "wap链接",
"weappUrl": "小程序链接",
"shareLink": "分享链接",
"copy": "复制",
"copySuccess": "复制成功",
"titlePlaceholder": "请输入页面名称",
"addDiyPage": "添加页面",
"diyPageDeleteTips": "确定要删除该自定义页面吗?",
"promote": "推广",
"share": "分享",
"shareSet": "分享设置",
"sharePage": "分享页面",
"wechat": "微信公众号",
"weapp": "微信小程序",
"shareTitle": "分享标题",
"shareTitlePlaceholder": "请输入分享标题",
"shareDesc": "分享描述",
"shareDescPlaceholder": "请输入分享描述",
"shareImageUrl": "分享图片"
}

View File

@ -0,0 +1,57 @@
{
"siteId":"站点id",
"title":"页面名称",
"name":"页面标识",
"type":"页面类型",
"value":"页面数据json格式",
"isDefault":"是否默认页面10否",
"visitCount":"访问量",
"siteIdPlaceholder":"请输入站点id",
"titlePlaceholder":"请输入页面名称",
"namePlaceholder":"请输入页面标识",
"typePlaceholder":"请输入页面类型",
"valuePlaceholder":"请输入页面数据json格式",
"isDefaultPlaceholder":"请输入是否默认页面10否",
"visitCountPlaceholder":"请输入访问量",
"createTimePlaceholder":"请输入创建时间",
"updateTimePlaceholder":"请输入更新时间",
"addDiyPage":"添加自定义页面",
"updateDiyPage":"编辑自定义页面",
"diyPageDeleteTips":"确定要删除该自定义页面吗?",
"leastTwoNav":"至少添加2个导航",
"pleaseUpload":"请上传第[",
"pleaseEnter":"请输入第[",
"pleaseChoose":"请选择第[",
"navIcon":"]个图标",
"navSelectIcon":"]个选中图标",
"navTitle":"]个导航标题",
"navLink":"]个导航链接",
"backgroundColor":"背景颜色",
"imageText":"图文",
"image":"图片",
"text":"文字",
"textColor":"文字颜色",
"textSelectColor":"文字选中颜色",
"reset":"重置",
"navType":"导航类型",
"styleSet":"样式设置",
"addnav":"添加导航",
"linkPlaceholder":"请选择链接",
"navLinkOne":"导航链接",
"navTitleOne":"导航标题",
"titleContent":"请输入标题内容",
"navIconOne":"导航图标",
"navImage":"导航图片",
"bottomNav":"底部导航",
"bottomNavHint":"设置至少添加2个导航最多添加5个导航",
"selectLinkTips":"选择链接",
"selectLinkConfirm":"确定",
"uploadImgUnselected":"未选中",
"uploadImgSelected":"选中"
}

View File

@ -0,0 +1,19 @@
{
"orderInfo":"订单详情",
"orderDiscountMoney":"优惠金额",
"ip":"下单IP",
"payTime":"支付时间",
"remark":"商家留言",
"memberMessage":"买家留言",
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间"
}

View File

@ -0,0 +1,14 @@
{
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择"
}

View File

@ -0,0 +1,24 @@
{
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"applyTime":"申请时间",
"withdrawalStatus":"提现状态",
"actualTransferAmount":"实际转账金额",
"withdrawalCommission":"提现手续费",
"applicationForWithdrawalAmount":"申请提现金额",
"withdrawalMethod":"提现方式",
"memberInfo":"会员信息",
"toBeTransferred":"待转账",
"transferred":"已转账",
"turnDown":"拒绝"
}

View File

@ -0,0 +1,32 @@
{
"todayData": "今日数据",
"memberNumb": "会员数量",
"orderMoney": "订单金额",
"numberOfSites": "站点数量",
"numberOfVisitors": "访客数量",
"commonlyUsedFunction": "常用功能",
"articleList": "文章列表",
"memberManagement": "会员管理",
"balanceAccount": "余额账户",
"administrator": "管理员",
"WebDecoration": "网站装修",
"accessMessage": "访问消息",
"memberDistribution": "会员分布",
"systemInfo": "系统环境",
"os": "操作系统",
"phpVersions": "PHP版本号",
"productionEnvironment": "生产环境",
"versionsInfo": "版本信息",
"versions": "当前版本",
"frame": "基于框架",
"channel": "获取渠道",
"serviceSupport": "服务支持",
"officialWbsite": "官网",
"pageView": "访问量",
"siteInfo":"站点信息",
"siteName":"站点名称",
"groupName":"站点套餐",
"expireTime":"过期时间",
"permanent":"永久",
"statusName":"站点状态"
}

View File

@ -0,0 +1,9 @@
{
"siteTitle": "Niucloud Admin",
"siteDesc": "基于thinkphp6+elementplus+typescript等技术的多端saas通用管理框架采用restful的api接口设计前后端完全分离同时支持多语言开发。",
"logging": "登录中",
"platform": "管理端",
"login" : "登录",
"userPlaceholder": "请输入您的账号",
"passwordPlaceholder": "请输入您的密码"
}

Some files were not shown because too many files have changed in this diff Show More