release(custom): i

This commit is contained in:
2023-10-10 15:16:06 +08:00
parent 5d1b96be72
commit a95a8ac0d1
50 changed files with 15273 additions and 4752 deletions

View File

@@ -1,23 +1,26 @@
module.exports = {
types: [
{ value: 'feat', name:'feat: 新增功能' },
{ value: 'fix', name:'fix: 修复bug' },
{ value: 'docs', name:'docs: 文档变更' },
{ value: 'style', name:'style: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: 'refactor', name:'refactor: 代码重构(不包括 bug 修复、功能新增)' },
{ value: 'perf', name:'perf: 性能优化' },
{ value: 'test', name:'test: 添加、修改测试用例' },
{ value: 'build', name:'build: 构建流程、外部依赖变更(如升级 npm 包、修改 脚手架 配置等)' },
{ value: 'ci', name:'ci: 修改 CI 配置、脚本' },
{ value: 'chore', name:'chore: 构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
{ value: 'revert', name:'revert: 回滚 commit' },
{ value: 'wip', name:'wip: 开发中' },
{ value: 'mod', name:'mod: 不确定分类的修改' },
{ value: 'release', name:'release: 发布' },
{ value: 'feat', name: 'feat: 新增功能' },
{ value: 'fix', name: 'fix: 修复bug' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' },
{ value: 'perf', name: 'perf: 性能优化' },
{ value: 'test', name: 'test: 添加、修改测试用例' },
{
value: 'build',
name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 脚手架 配置等)',
},
{ value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
{ value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
{ value: 'revert', name: 'revert: 回滚 commit' },
{ value: 'wip', name: 'wip: 开发中' },
{ value: 'mod', name: 'mod: 不确定分类的修改' },
{ value: 'release', name: 'release: 发布' },
],
scopes: [
['custom', '自定义'],
['projects', '项目搭建'],
['projects', '项目搭建'],
['components', '组件相关'],
['utils', 'utils 相关'],
['styles', '样式相关'],
@@ -26,7 +29,7 @@ module.exports = {
].map(([value, description]) => {
return {
value,
name: `${value.padEnd(30)} (${description})`
name: `${value.padEnd(30)} (${description})`,
}
}),
messages: {
@@ -37,9 +40,9 @@ module.exports = {
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:',
breaking: '列举非兼容性重大的变更(可选):',
footer: '列举出所有变更的 Issues Closed可选。 例如: #31, #34',
confirmCommit: '确认提交?'
confirmCommit: '确认提交?',
},
allowBreakingChanges: ['feat', 'fix'],
subjectLimit: 100,
breaklineChar: '|'
breaklineChar: '|',
}

View File

@@ -1,7 +1,7 @@
VITE_PUBLIC_PATH = '/'
# 是否启用MOCK
VITE_USE_MOCK = true
# base api
VITE_BASE_API = '/api'
VITE_PUBLIC_PATH = '/static/mer'
# 是否启用MOCK
VITE_USE_MOCK = true
# base api
VITE_BASE_API = 'https://test.wanzhuanyongcheng.cn/store'

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/jsLinters/eslint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

6
.idea/prettier.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

View File

@@ -4,6 +4,6 @@
"antfu.iconify",
"mikestead.dotenv",
"sdras.vue-vscode-snippets",
"cipchk.cssrem",
"cipchk.cssrem"
]
}

View File

@@ -4,6 +4,7 @@ import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver'
import mkcert from 'vite-plugin-mkcert'
/**
* * unplugin-icons插件自动引入iconify图标
@@ -43,4 +44,5 @@ export default [
inject: 'body-last',
customDomId: '__CUSTOM_SVG_ICON__',
}),
mkcert(),
]

View File

@@ -1,3 +1,3 @@
推荐阅读作者在掘金的文章:
[保熟的UnoCSS使用指北优雅使用antfu大佬的原子化CSS](https://juejin.cn/post/7142466784971456548)
[保熟的 UnoCSS 使用指北,优雅使用 antfu 大佬的原子化 CSS](https://juejin.cn/post/7142466784971456548)

View File

@@ -14,7 +14,7 @@
`<icon-[iconify图标名称]`
```html
<icon-ant-design:fullscreen-exit-outlined />
<icon-ant-design:fullscreen-exit-outlined />
<icon-ant-design:fullscreen-outlined />
```
@@ -37,4 +37,4 @@
<TheIcon icon="logo" type="custom" />
```
封装组件参看 src/components/icon
封装组件参看 src/components/icon

View File

@@ -1,6 +1,6 @@
## 安装pnpm
## 安装 pnpm
### 使用Corepack安装推荐
### 使用 Corepack 安装(推荐)
从 v16.13 开始Node.js 发布了 Corepack 来管理包管理器。 这是一项实验性功能,需要通过运行如下脚本来启用它:
@@ -8,17 +8,19 @@
npx corepack enable // 可能需要管理员权限
```
这将自动在您的系统上安装 pnpm。 但是,它可能不是最新版本的 pnpm。 若要升级,请检查[最新的 pnpm 版本](https://github.com/pnpm/pnpm/releases/latest) 并运行,如 7.14.0
这将自动在您的系统上安装 pnpm。 但是,它可能不是最新版本的 pnpm。 若要升级,请检查[最新的 pnpm 版本](https://github.com/pnpm/pnpm/releases/latest) 并运行,如 7.14.0
```
corepack prepare pnpm@7.14.0 --activate
```
如果是 Node.js v16.17 或者更新的版本,可以直接安装最新版本的 pnpm
```
corepack prepare pnpm@latest --activate
```
### 使用npm安装
### 使用 npm 安装
```
npm i -g pnpm
@@ -29,4 +31,4 @@ npm i -g pnpm
```
npm uninstall -g pnpm
npm i -g pnpm
```
```

View File

@@ -11,7 +11,8 @@
"lint:fix": "eslint --fix --ext .js,.vue .",
"lint:staged": "lint-staged",
"prepare": "husky install",
"preview": "vite preview"
"preview": "vite preview",
"lf": "npx prettier --write --end-of-line lf ."
},
"lint-staged": {
"*.{js,vue}": [
@@ -31,28 +32,28 @@
]
},
"dependencies": {
"@unocss/eslint-config": "^0.55.3",
"@vueuse/core": "^10.4.0",
"@unocss/eslint-config": "^0.55.7",
"@vueuse/core": "^10.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "5.1.12",
"axios": "^1.5.0",
"dayjs": "^1.11.9",
"axios": "^1.5.1",
"dayjs": "^1.11.10",
"lodash-es": "^4.17.21",
"md-editor-v3": "^4.4.0",
"md-editor-v3": "^4.7.0",
"mockjs": "^1.1.0",
"pinia": "^2.1.6",
"vite": "^4.4.9",
"vite": "^4.4.11",
"vue": "3.3.4",
"vue-router": "^4.2.4",
"vue-router": "^4.2.5",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^17.7.1",
"@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0",
"@iconify/json": "^2.2.106",
"@iconify/json": "^2.2.126",
"@iconify/vue": "^4.1.1",
"@unocss/preset-rem-to-px": "^0.55.3",
"@vitejs/plugin-vue": "^4.3.3",
"@unocss/preset-rem-to-px": "^0.55.7",
"@vitejs/plugin-vue": "^4.4.0",
"@vue/compiler-sfc": "^3.3.4",
"@zclzone/eslint-config": "^0.0.4",
"chalk": "^5.3.0",
@@ -64,15 +65,16 @@
"fs-extra": "^11.1.1",
"husky": "^8.0.3",
"lint-staged": "^14.0.1",
"naive-ui": "^2.34.4",
"naive-ui": "^2.35.0",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.66.1",
"sass": "^1.69.0",
"unocss": "0.55.3",
"unplugin-auto-import": "^0.16.6",
"unplugin-icons": "^0.16.5",
"unplugin-vue-components": "^0.25.1",
"unplugin-icons": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mkcert": "^1.16.0",
"vite-plugin-mock": "2.9.6",
"vite-plugin-svg-icons": "^2.0.1"
}

15687
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -3,4 +3,13 @@ import { request } from '@/utils'
export default {
getUser: () => request.get('/user'),
refreshToken: () => request.post('/auth/refreshToken', null, { noNeedTip: true }),
// 获取商家信息
getMerchantInfo: () => request.post('/info'),
// 上传图片
uploadImg: (data) =>
request.post('/upload', data, {
headers: {
'Content-Type': 'multipart/form-data',
},
}),
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -21,7 +21,7 @@ import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, shallowRef, ref } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { getToken } from '@/utils'
import api from '@/api'
const props = defineProps({
valueHtml: {
@@ -36,64 +36,40 @@ const editorRef = shallowRef()
const _ref = ref(null)
const Token = getToken()
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入商品详情...', MENU_CONF: {} }
const htmlValue = computed({
get() {
console.log('get', props.valueHtml)
return props.valueHtml
},
set(value) {
console.log('set', props.valueHtml)
emit('update:valueHtml', value)
},
})
editorConfig.MENU_CONF['uploadImage'] = {
// 其他配置...
// 小于该值就插入 base64 格式(而不上传),默认为 0
fieldName: 'file',
server: '/store/upload',
allowedFileTypes: ['image/*'],
headers: {
Authorization: `Bearer ${Token}`,
},
base64LimitSize: 5 * 1024, // 5kb
// 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒
// 自定义插入图片
customInsert(res, insertFn) {
if (res.code === 200) {
console.log(res)
$message.success('上传成功')
insertFn(res.data.data)
}
async customUpload(file, insertFn) {
await upFile(file, insertFn)
},
}
editorConfig.MENU_CONF['uploadVideo'] = {
server: '/store/upload',
fieldName: 'file',
allowedFileTypes: ['video/*'],
headers: {
Authorization: `Bearer ${Token}`,
},
// 单个文件上传成功之后
customInsert(res, insertFn) {
console.log(res)
// 如果成功了,就把视频的地址,插入到编辑器中
insertFn(res.data.data)
async customUpload(file, insertFn) {
await upFile(file, insertFn)
},
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
console.log('销毁')
const editor = editorRef.value
if (editor == null) return
editor.destroy()
@@ -105,6 +81,13 @@ const handleCreated = (editor) => {
_ref.value.style.zIndex = 10
})
}
const upFile = async (file, insertFn) => {
const formData = new FormData()
formData.append('file', file)
const res = await api.uploadImg(formData)
insertFn(res.data.data)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,12 +1,11 @@
<template>
<n-upload
v-model:file-list="List"
action="/store/upload"
:headers="headers"
:max="max"
list-type="image-card"
:custom-request="customRequest"
:multiple="multiple"
@before-upload="beforeUpload"
@finish="handleFinish"
@error="handleError"
>
点击上传
@@ -14,9 +13,7 @@
</template>
<script setup>
import { getToken } from '@/utils'
const token = getToken()
import api from '@/api'
const prop = defineProps({
list: {
@@ -27,14 +24,14 @@ const prop = defineProps({
type: Number,
default: 1,
},
multiple: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:list'])
const headers = ref({
Authorization: `Bearer ${token}`,
})
const List = computed({
get() {
return prop.list
@@ -44,12 +41,6 @@ const List = computed({
},
})
const handleFinish = ({ file, event }) => {
const res = JSON.parse(event.target.response)
file.url = res.data.data
return file
}
const handleError = () => {
$message.error('上传失败')
}
@@ -62,6 +53,14 @@ const beforeUpload = (data) => {
}
return true
}
const customRequest = async ({ file, onFinish }) => {
const formData = new FormData()
formData.append('file', file.file)
const res = await api.uploadImg(formData)
$message.success(res.msg)
onFinish(res.data.data)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -109,6 +109,7 @@ function handleMenuSelect(key, item) {
left: 5px;
right: 5px;
}
&.n-menu-item-content--selected,
&:hover {
&::before {

View File

@@ -11,6 +11,9 @@ export function createPermissionGuard(router) {
return { path: 'login', query: { ...to.query, redirect: to.path } }
}
/** 单点登录的情况 */
if (to.query.tk) return true
/** 有token的情况 */
if (to.path === '/login') return { path: '/' }

View File

@@ -18,7 +18,10 @@ export const useUserStore = defineStore('user', {
return this.userInfo?.name
},
avatar() {
return this.userInfo?.avatar
return (
this.userInfo?.avatar ||
'https://pic3.58cdn.com.cn/nowater/webim/big/n_v21bc7874294754e63a22b80febac9cf51.jpg'
)
},
role() {
return this.userInfo?.role || []

View File

@@ -1,5 +1,4 @@
import { lStorage } from '@/utils'
import api from '@/api'
const TOKEN_CODE = 'mer_token'
const DURATION = 6 * 60 * 60
@@ -17,17 +16,17 @@ export function removeToken() {
}
export async function refreshAccessToken() {
const tokenItem = lStorage.getItem(TOKEN_CODE)
if (!tokenItem) {
return
}
const { time } = tokenItem
// token生成或者刷新后30分钟内不执行刷新
if (new Date().getTime() - time <= 1000 * 60 * 30) return
try {
const res = await api.refreshToken()
setToken(res.data.token)
} catch (error) {
console.error(error)
}
// const tokenItem = lStorage.getItem(TOKEN_CODE)
// if (!tokenItem) {
// return
// }
// const { time } = tokenItem
// // token生成或者刷新后30分钟内不执行刷新
// if (new Date().getTime() - time <= 1000 * 60 * 30) return
// try {
// const res = await api.refreshToken()
// setToken(res.data.token)
// } catch (error) {
// console.error(error)
// }
}

View File

@@ -32,7 +32,7 @@ export function resResolve(response) {
const code = data?.code ?? status
/** 根据code处理对应的操作并返回处理后的message */
const message = resolveResError(code, data?.message ?? statusText)
const message = resolveResError(code, data?.msg ?? statusText)
/** 需要错误提醒 */
!config.noNeedTip && window.$message?.error(message)

View File

@@ -1,6 +1,6 @@
import { request } from '@/utils'
export default {
getGoodClass: (data) => request.post('/goods/class', data),
getGoodClass: (data) => request.post('/classify', data),
addGoods: (data) => request.post('/goods/edit', data),
}

View File

@@ -20,7 +20,7 @@
<Upload v-model:list="model.cover" />
</n-form-item-gi>
<n-form-item-gi :span="12" label="商品轮播图:" path="rotation">
<Upload v-model:list="model.rotation" :max="5" />
<Upload v-model:list="model.rotation" :max="5" multiple />
</n-form-item-gi>
<n-form-item-gi :span="12" label="商品价格:" path="number">
<n-input-number v-model:value="model.number" placeholder="输入商品价格" />
@@ -31,11 +31,12 @@
<n-form-item-gi :span="12" label="商品库存:" path="stock">
<n-input-number v-model:value="model.stock" placeholder="输入商品库存" />
</n-form-item-gi>
<n-form-item-gi :span="12" label="商品简介:" path="profile">
<n-input v-model:value="model.profile" type="textarea" placeholder="输入商品简介" />
</n-form-item-gi>
<n-form-item-gi :span="12" label="上架状态:" path="state">
<n-switch v-model:value="model.state" :checked-value="1" :unchecked-value="2" />
</n-form-item-gi>
<n-form-item-gi :span="12" label="商品详情:" path="details">
<Editor v-model:valueHtml="model.details" />
</n-form-item-gi>
@@ -67,6 +68,7 @@ const model = ref({
stock: null,
number: null,
market_num: null,
state: 2,
})
const rules = {

View File

@@ -0,0 +1,7 @@
import { request } from '@/utils'
export default {
getGoodClass: (data) => request.post('/classify', data),
// 添加或修改
addGoodClass: (data) => request.post('/classify/edit', data),
}

View File

@@ -0,0 +1,171 @@
<template>
<CommonPage show-footer :title="$route.title">
<n-button type="primary" @click="addClass(1)">添加商品分类</n-button>
<n-data-table
class="mt-10"
:loading="loading"
:columns="columns"
:data="data"
:pagination="pagination"
:bordered="false"
remote
/>
<!-- 添加/编辑对话框 -->
<n-modal v-model:show="showModal">
<n-card
style="width: 500px"
:title="showModalTitle"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-form ref="formRef" :model="model" :rules="rules" label-placement="left">
<n-grid :cols="24" :x-gap="24">
<n-form-item-gi label="商品分类:" :span="14" path="name">
<n-input v-model:value="model.name" placeholder="请输入商品分类" />
</n-form-item-gi>
<n-form-item-gi label="是否显示:" :span="14" path="status">
<n-switch v-model:value="model.status" :unchecked-value="2" :checked-value="1" />
</n-form-item-gi>
<n-form-item-gi :span="14">
<div class="m-auto">
<n-button type="primary" @click="submit">提交</n-button>
<n-button ml-10 @click="clear">取消</n-button>
</div>
</n-form-item-gi>
</n-grid>
</n-form>
</n-card>
</n-modal>
</CommonPage>
</template>
<script setup>
import api from './api.js'
import { NTag, NButton } from 'naive-ui'
const loading = ref(false)
const data = ref([])
const showModal = ref(false)
const showModalTitle = ref('')
const model = ref({
name: '',
status: 2,
})
const columns = ref([
{
title: '分类名称',
key: 'name',
align: 'center',
},
{
title: '是否显示',
slot: 'status',
align: 'center',
render: (row) => {
return h(
NTag,
{
type: row.status === 1 ? 'success' : 'warning',
bordered: false,
},
{
default: () => (row.status === 1 ? '显示' : '隐藏'),
}
)
},
},
{
title: '操作',
slot: 'action',
align: 'center',
render: (row) => {
return [
h(
NButton,
{
text: true,
size: 'small',
type: 'primary',
onClick: () => {
addClass(2, row)
},
},
{
default: () => '编辑',
}
),
]
},
},
])
const formRef = ref(null)
const rules = {
name: {
required: true,
message: '请输入商品分类',
trigger: 'blur',
},
}
const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
onChange: (page) => {
pagination.value.page = page
get_data()
},
})
onMounted(() => {
get_data()
})
const get_data = async () => {
loading.value = true
const res = await api.getGoodClass({
PageNum: pagination.value.page,
PageSize: pagination.value.pageSize,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
loading.value = false
}
const addClass = (e, row = {}) => {
showModalTitle.value = e === 1 ? '添加商品分类' : '编辑商品分类'
if (e === 2) model.value = { ...row }
showModal.value = true
}
const submit = () => {
formRef.value?.validate(async (errors) => {
if (!errors) {
const res = await api.addGoodClass(model.value)
$message.success(res.msg)
clear()
}
})
}
const clear = () => {
formRef.value.restoreValidation()
model.value = {
name: '',
status: 2,
}
showModal.value = false
get_data()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -2,5 +2,5 @@ import { request } from '@/utils'
export default {
getGoods: (data) => request.post('/goods', data),
getGoodClass: (data) => request.post('/goods/class', data),
getGoodClass: (data) => request.post('/classify', data),
}

View File

@@ -14,8 +14,9 @@
<script setup>
import { useRouter } from 'vue-router'
import { NButton, NEllipsis } from 'naive-ui'
import { NButton, NEllipsis, NImage, NSwitch } from 'naive-ui'
import api from './api'
import api1 from '../add/api'
import { useGoodsStore } from '@/store/modules/goods'
const loading = ref(false)
@@ -31,12 +32,9 @@ const columns = ref([
align: 'center',
slot: 'cover',
render(row) {
return h('img', {
return h(NImage, {
width: '50',
src: row.cover,
style: {
width: '30px',
height: '30px',
},
})
},
},
@@ -74,13 +72,34 @@ const columns = ref([
key: 'stock',
},
{
title: '状态',
title: '审核状态',
align: 'center',
slot: 'status',
render(row) {
return h('span', row.status === 1 ? '已审核' : row.status === 2 ? '已拒绝' : '待审核')
},
},
{
title: '是否上架',
align: 'center',
slot: 'state',
render(row) {
return [
h(NSwitch, {
checkedValue: 1,
uncheckedValue: 2,
value: row.state,
onUpdateValue: async (val) => {
await api1.addGoods({
...row,
state: val,
})
getList()
},
}),
]
},
},
{
title: '备注',
key: 'notes',
@@ -91,19 +110,17 @@ const columns = ref([
align: 'center',
slot: 'action',
render(row) {
if (row.status === 1 || row.status === 2) {
return [
h(
NButton,
{
type: 'primary',
size: 'small',
onClick: () => toEdit(row),
},
() => '编辑'
),
]
}
return [
h(
NButton,
{
type: 'primary',
size: 'small',
onClick: () => toEdit(row),
},
() => '编辑'
),
]
},
},
])

View File

@@ -5,6 +5,11 @@ export default {
path: '/goods',
component: Layout,
redirect: '/goods_list',
meta: {
title: '商品管理',
icon: 'mdi:account-multiple',
order: 10,
},
children: [
{
name: 'Goodslist',
@@ -13,7 +18,17 @@ export default {
meta: {
title: '商品列表',
icon: 'mdi:account-multiple',
order: 10,
order: 4,
},
},
{
name: 'Goodsclass',
path: 'goods_class',
component: () => import('./class/index.vue'),
meta: {
title: '商品分类',
icon: 'mdi:account-multiple',
order: 4,
},
},
{

View File

@@ -67,6 +67,9 @@ import { useStorage } from '@vueuse/core'
import bgImg from '@/assets/images/login_bg.webp'
import api from './api'
import { addDynamicRoutes } from '@/router'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const title = import.meta.env.VITE_TITLE
@@ -80,6 +83,26 @@ const loginInfo = ref({
initLoginInfo()
const easyLogin = async () => {
userStore.logout()
if (query.tk && query.type) {
console.log(query)
$message.success('登录成功')
setToken(query.tk)
window.localStorage.setItem('type', query.type)
await addDynamicRoutes()
if (query.redirect) {
const path = query.redirect
Reflect.deleteProperty(query, 'redirect')
router.push({ path, query })
} else {
router.push('/')
}
}
}
easyLogin()
function initLoginInfo() {
const localLoginInfo = lStorage.get('loginInfo')
if (localLoginInfo) {

View File

@@ -1,6 +1,72 @@
<template>
<CommonPage show-footer :title="$route.title">
<n-row gutter="12">
<n-col :span="24">
<div flex>
<n-card w-500>
<n-statistic label="订单流水" tabular-nums>
<n-number-animation :from="0" :to="cardData.total" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单佣金" tabular-nums>
<n-number-animation :from="0" :to="cardData.service" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单数量" tabular-nums>
<n-number-animation :from="0" :to="cardData.count" />
</n-statistic>
</n-card>
</div>
</n-col>
<n-col :span="24" mt-10>
<div>
<span>订单状态</span>
<n-radio-group v-model:value="queryData.status">
<n-radio-button
v-for="song in songs"
:key="song.value"
:value="song.value"
:label="song.label"
/>
</n-radio-group>
</div>
</n-col>
<n-col :span="24">
<div mt-10 flex items-center>
<div w-100>关键字搜索</div>
<n-input-group>
<n-select
v-model:value="queryData.selectKey"
:style="{ width: '7%' }"
:options="selectOptions"
placeholder="请选择"
/>
<n-input v-model:value="queryData.word" :style="{ width: '20%' }" />
</n-input-group>
</div>
</n-col>
<n-col :span="10">
<div mt-10 flex items-center>
<span w-100>订单时间</span>
<n-date-picker
v-model:formatted-value="queryData.time"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
clearable
/>
</div>
</n-col>
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="getList">搜索</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-col>
</n-row>
<n-data-table
class="mt-5"
:loading="loading"
:columns="columns"
:data="data"
@@ -13,9 +79,55 @@
<script setup>
import api from './api'
import { NEllipsis } from 'naive-ui'
const loading = ref(false)
const queryData = ref({
status: '',
time: null,
word: '',
selectKey: null,
})
const songs = ref([
{
value: 1,
label: '待付款',
},
{
value: 2,
label: '待核销',
},
{
value: 3,
label: '已核销',
},
{
value: 4,
label: '已过期',
},
])
const selectOptions = ref([
{
value: 0,
label: '商品名称',
},
{
value: 1,
label: '用户昵称',
},
{
value: 2,
label: '手机号',
},
{
value: 3,
label: '订单号',
},
])
const columns = ref([
{
title: '订单号',
@@ -28,15 +140,41 @@ const columns = ref([
key: 'user_name',
},
{
title: '商品名称',
title: '手机号',
align: 'center',
key: 'goods_name',
key: 'phone',
},
{
title: '商品价格',
title: '商品名称',
align: 'center',
slot: 'goods_name',
render: (row) => {
return h(
NEllipsis,
{
style: 'max-width: 240px',
},
{
default: () => row.goods_name,
}
)
},
},
{
title: '商品数量',
align: 'center',
key: 'count',
},
{
title: '商品总价',
align: 'center',
key: 'number',
},
{
title: '订单佣金',
align: 'center',
key: 'commission',
},
{
title: '订单状态',
align: 'center',
@@ -55,17 +193,33 @@ const columns = ref([
},
},
{
title: '操作',
title: '下单时间',
align: 'center',
slot: 'action',
render(row) {
console.log(row)
},
key: 'add_time',
},
{
title: '核销时间',
align: 'center',
key: 'cancel_time',
},
// {
// title: '操作',
// align: 'center',
// slot: 'action',
// render() {
// // console.log(row)
// },
// },
])
const data = ref([])
const cardData = ref({
total: 0,
service: 0,
count: 0,
})
const pagination = ref({
page: 1,
pageSize: 10,
@@ -88,18 +242,51 @@ onMounted(() => {
const getList = async () => {
loading.value = true
try {
const query_data = {
Status: queryData.value.status || '',
StartTime: queryData.value.time === null ? '' : queryData.value.time[0] || '',
EndTime: queryData.value.time === null ? '' : queryData.value.time[1] || '',
}
switch (queryData.value.selectKey) {
case 0:
query_data['GoodsName'] = queryData.value.word
break
case 1:
query_data['UserName'] = queryData.value.word
break
case 2:
query_data['Phone'] = queryData.value.word
break
case 3:
query_data['Oid'] = queryData.value.word
break
}
const res = await api.getOrder({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
...query_data,
})
console.log(res)
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
cardData.value.total = res.data.number
cardData.value.service = res.data.commission
cardData.value.count = res.data.total
} catch (error) {
$message.error(error.msg)
throw error
}
loading.value = false
}
const clear = () => {
queryData.value = {
status: '',
time: null,
word: '',
selectKey: null,
}
getList()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,5 @@
import { request } from '@/utils'
export default {
getOrder: (data) => request.post('/order', data),
}

View File

@@ -0,0 +1,282 @@
<template>
<CommonPage show-footer :title="$route.title">
<n-row gutter="12">
<n-col :span="24">
<div flex>
<n-card w-500>
<n-statistic label="订单流水" tabular-nums>
<n-number-animation :from="0" :to="cardData.total" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单数量" tabular-nums>
<n-number-animation :from="0" :to="cardData.count" />
</n-statistic>
</n-card>
</div>
</n-col>
<n-col :span="24" mt-10>
<div>
<span>订单状态</span>
<n-radio-group v-model:value="queryData.status">
<n-radio-button
v-for="song in songs"
:key="song.value"
:value="song.value"
:label="song.label"
/>
</n-radio-group>
</div>
</n-col>
<n-col :span="24">
<div mt-10 flex items-center>
<div w-100>关键字搜索</div>
<n-input-group>
<n-select
v-model:value="queryData.selectKey"
:style="{ width: '7%' }"
:options="selectOptions"
placeholder="请选择"
/>
<n-input v-model:value="queryData.word" :style="{ width: '20%' }" />
</n-input-group>
</div>
</n-col>
<n-col :span="10">
<div mt-10 flex items-center>
<span w-100>订单时间</span>
<n-date-picker
v-model:formatted-value="queryData.time"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
clearable
/>
</div>
</n-col>
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="getList">搜索</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-col>
</n-row>
<n-data-table
class="mt-5"
:loading="loading"
:columns="columns"
:data="data"
:pagination="pagination"
:bordered="false"
remote
/>
</CommonPage>
</template>
<script setup>
import api from './api'
import { NEllipsis } from 'naive-ui'
const loading = ref(false)
const queryData = ref({
status: '',
time: null,
word: '',
selectKey: null,
})
const songs = ref([
{
value: 1,
label: '待付款',
},
{
value: 2,
label: '待核销',
},
{
value: 3,
label: '已核销',
},
{
value: 4,
label: '已过期',
},
])
const selectOptions = ref([
{
value: 0,
label: '商品名称',
},
{
value: 1,
label: '用户昵称',
},
{
value: 2,
label: '手机号',
},
{
value: 3,
label: '订单号',
},
])
const columns = ref([
{
title: '订单号',
align: 'center',
key: 'oid',
},
{
title: '用户',
align: 'center',
key: 'user_name',
},
{
title: '手机号',
align: 'center',
key: 'phone',
},
{
title: '商品名称',
align: 'center',
slot: 'goods_name',
render: (row) => {
return h(
NEllipsis,
{
style: 'max-width: 240px',
},
{
default: () => row.goods_name,
}
)
},
},
{
title: '商品数量',
align: 'center',
key: 'count',
},
{
title: '商品总价',
align: 'center',
key: 'number',
},
{
title: '订单状态',
align: 'center',
slot: 'status',
render(row) {
switch (row.status) {
case 0:
return h('span', '待付款')
case 1:
return h('span', '待使用')
case 2:
return h('span', '已完成')
case 3:
return h('span', '已过期')
}
},
},
{
title: '下单时间',
align: 'center',
key: 'add_time',
},
{
title: '核销时间',
align: 'center',
key: 'cancel_time',
},
// {
// title: '操作',
// align: 'center',
// slot: 'action',
// render() {
// // console.log(row)
// },
// },
])
const data = ref([])
const cardData = ref({
total: 0,
service: 0,
count: 0,
})
const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
onChange: (page) => {
pagination.value.page = page
getList()
},
onUpdatePageSize: (pageSize) => {
pagination.value.pageSize = pageSize
pagination.value.page = 1
getList()
},
})
onMounted(() => {
getList()
})
const getList = async () => {
loading.value = true
try {
const query_data = {
Status: queryData.value.status || '',
StartTime: queryData.value.time === null ? '' : queryData.value.time[0] || '',
EndTime: queryData.value.time === null ? '' : queryData.value.time[1] || '',
}
switch (queryData.value.selectKey) {
case 0:
query_data['GoodsName'] = queryData.value.word
break
case 1:
query_data['UserName'] = queryData.value.word
break
case 2:
query_data['Phone'] = queryData.value.word
break
case 3:
query_data['Oid'] = queryData.value.word
break
}
const res = await api.getOrder({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
...query_data,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
cardData.value.total = res.data.number
cardData.value.service = res.data.commission
cardData.value.count = res.data.total
} catch (error) {
$message.error(error.msg)
throw error
}
loading.value = false
}
const clear = () => {
queryData.value = {
status: '',
time: null,
word: '',
selectKey: null,
}
getList()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -8,17 +8,28 @@ export default {
meta: {
title: '订单管理',
icon: 'majesticons:compass-line',
order: 1,
// requireAuth: true,
// role: ['1'],
order: 10,
},
children: [
{
name: 'OrderList',
path: 'order_list',
name: 'gyList',
path: 'gy_list',
component: () => import('./index1/index.vue'),
meta: {
requireAuth: true,
role: ['1'],
title: '订单列表',
icon: 'material-symbols:auto-awesome-outline-rounded',
},
},
{
name: 'dhList',
path: 'dh_list',
component: () => import('./index/index.vue'),
meta: {
requireAuth: true,
title: '订单列表',
role: ['2'],
icon: 'material-symbols:auto-awesome-outline-rounded',
},
},

View File

@@ -1,5 +1,5 @@
import { request } from '@/utils'
export default {
getList: (data) => request.post('/order', data),
getList: (data) => request.post('/record', data),
}

View File

@@ -1,7 +1,58 @@
<template>
<!-- <div> -->
<CommonPage show-footer :title="$route.title">
<n-row gutter="12">
<n-col :span="24">
<div flex>
<n-card w-500>
<n-statistic label="订单流水(积分)" tabular-nums>
<n-number-animation :from="0" :to="cardData.total" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单服务费(积分)" tabular-nums>
<n-number-animation :from="0" :to="cardData.service" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单数量" tabular-nums>
<n-number-animation :from="0" :to="cardData.count" />
</n-statistic>
</n-card>
</div>
</n-col>
<n-col :span="8">
<div mt-10 flex items-center>
<span>积分渠道</span>
<n-radio-group v-model:value="queryData.type">
<n-radio-button
v-for="song in songs"
:key="song.value"
:value="song.value"
:label="song.label"
/>
</n-radio-group>
</div>
</n-col>
<n-col :span="24">
<div mt-10 flex items-center>
<span>条件筛选</span>
<n-date-picker
v-model:formatted-value="queryData.time"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
clearable
/>
</div>
</n-col>
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="getList">搜索</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-col>
</n-row>
<n-data-table
class="mt-10"
:loading="loading"
:columns="columns"
:data="data"
@@ -10,35 +61,48 @@
remote
/>
</CommonPage>
<!-- </div> -->
</template>
<script setup>
import api from './api'
import { NEllipsis } from 'naive-ui'
const loading = ref(false)
const columns = ref([
const songs = ref([
{
title: 'ID',
key: 'ID',
align: 'center',
label: '订单',
value: 1,
},
{
title: '商品名称',
key: 'goods_name',
align: 'center',
label: '提现',
value: 2,
},
{
title: '用户名称',
key: 'user_name',
align: 'center',
},
{
title: '获取积分',
key: 'number',
align: 'center',
label: '增加(含驳回)',
value: 3,
},
])
const queryData = ref({
type: 1,
time: null,
})
const cardData = ref({
total: 0,
service: 0,
count: 0,
})
// watch(
// () => queryData.value.type,
// () => {
// getList()
// }
// )
const columns = ref([])
const data = ref([])
const pagination = ref({
@@ -63,18 +127,137 @@ onMounted(() => {
const getList = async () => {
loading.value = true
try {
switch (queryData.value.type) {
case 1:
columns.value = [
{
title: 'ID',
key: 'ID',
align: 'center',
},
{
title: '商品名称',
align: 'center',
slot: 'goods_name',
render: (row) => {
return h(
NEllipsis,
{
style: 'max-width: 240px',
},
{
default: () => row.goods_name,
}
)
},
},
{
title: '商品数量',
key: 'count',
align: 'center',
},
{
title: '用户名称',
key: 'user_name',
align: 'center',
},
{
title: '上次留存积分',
key: 'balance',
align: 'center',
},
{
title: '获取积分',
key: 'number',
align: 'center',
},
{
title: '服务费',
key: 'commission',
align: 'center',
},
{
title: '时间',
key: 'record_time',
align: 'center',
},
]
break
case 2:
columns.value = [
{
title: '单号',
key: 'oid',
align: 'center',
},
{
title: '上次留存积分',
key: 'balance',
align: 'center',
},
{
title: '扣除积分',
key: 'record_number',
align: 'center',
},
{
title: '时间',
key: 'record_time',
align: 'center',
},
]
break
case 3:
columns.value = [
{
title: '单号',
key: 'oid',
align: 'center',
},
{
title: '上次留存积分',
key: 'balance',
align: 'center',
},
{
title: '获取积分',
key: 'record_number',
align: 'center',
},
{
title: '时间',
key: 'record_time',
align: 'center',
},
]
break
}
const res = await api.getList({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
Type: queryData.value.type,
StartTime: queryData.value.time === null ? '' : queryData.value.time[0] || '',
EndTime: queryData.value.time === null ? '' : queryData.value.time[1] || '',
})
console.log(res)
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
cardData.value.total = res.data.number
cardData.value.service = res.data.commission
cardData.value.count = res.data.total
} catch (error) {
$message.error(error.msg)
}
loading.value = false
}
const clear = () => {
queryData.value = {
type: 1,
time: null,
}
getList()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -6,30 +6,32 @@ export default {
component: Layout,
redirect: '/settlement_list',
meta: {
title: '积分管理',
requireAuth: true,
order: 1,
icon: 'mdi:account-multiple',
order: 10,
role: ['2'],
},
children: [
{
name: 'Settlementlist',
path: 'settlement_list',
component: () => import('./index/index.vue'),
name: 'Jflist',
path: 'jf_list',
component: () => import('./jf_list/index.vue'),
meta: {
title: '积分明细',
icon: 'mdi:account-multiple',
order: 10,
},
},
// {
// name: 'Jflist',
// path: 'jf_list',
// component: () => import('./jf_list/index.vue'),
// meta: {
// title: '积分列表',
// icon: 'mdi:account-multiple',
// order: 10,
// },
// },
{
name: 'Txlist',
path: 'tx_list',
component: () => import('./tx_list/index.vue'),
meta: {
title: '积分提现',
icon: 'mdi:account-multiple',
order: 10,
},
},
],
}

View File

@@ -0,0 +1,7 @@
import { request } from '@/utils'
export default {
getList: (data) => request.post('/withdraw', data),
// 申请提现
apply: (data) => request.post('/withdraw/set', data),
}

View File

@@ -0,0 +1,194 @@
<template>
<!-- <div> -->
<CommonPage show-footer :title="$route.title">
<div w-1200 flex items-center justify-between>
<n-card class="w-300">
<n-statistic label="可提现积分">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
:from="0"
:to="userInfo.integral"
/>
</n-statistic>
</n-card>
<div w-100 text-center text-25>/</div>
<n-card class="w-300">
<n-statistic label="兑换比例">
<n-number-animation ref="numberAnimationInstRef" :precision="2" :from="0" :to="100" />
</n-statistic>
</n-card>
<div w-100 text-center text-25>=</div>
<n-card class="w-300">
<n-statistic label="CNY">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
:from="0"
:to="userInfo.integral / 100"
/>
</n-statistic>
</n-card>
<div ml-10 flex flex-col items-center justify-center>
<n-input-number v-model:value="formData.integral" clearable placeholder="请输入提现积分" />
<n-button mt-10 w-full type="primary" @click="ok">立即提现</n-button>
</div>
</div>
<n-data-table
class="mt-10"
:loading="loading"
:columns="columns"
:data="data"
:pagination="pagination"
:bordered="false"
remote
/>
</CommonPage>
<!-- </div> -->
</template>
<script setup>
import api from './api'
import comm from '@/api'
import { NTag, NImage } from 'naive-ui'
const loading = ref(false)
const columns = ref([
{
title: 'ID',
key: 'ID',
align: 'center',
},
{
title: '提现积分',
key: 'integral',
align: 'center',
},
{
title: '银行名称',
key: 'bank',
align: 'center',
},
{
title: '银行卡号',
key: 'bank_card',
align: 'center',
},
{
title: '账户名称',
key: 'bank_name',
align: 'center',
},
{
title: '法人',
key: 'bank_user',
align: 'center',
},
{
title: '提现状态',
slot: 'status',
align: 'center',
render: (row) => {
return h(
NTag,
{
type: row.status === 1 ? 'success' : row.status === 2 ? 'error' : 'warning',
},
{
default: () => (row.status === 1 ? '已审核' : row.status === 2 ? '已拒绝' : '待审核'),
}
)
},
},
{
title: '提现时间',
slot: 'add_time',
align: 'center',
render: (row) => {
return h(
'span',
{},
{
default: () => row.add_time,
}
)
},
},
{
title: '打款截图',
slot: 'img',
render: (row) => {
return h(NImage, {
src: row.status_img,
width: '50',
})
},
},
])
const data = ref([])
const formData = ref({
integral: null,
})
const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
onChange: (page) => {
pagination.value.page = page
getList()
},
onUpdatePageSize: (pageSize) => {
pagination.value.pageSize = pageSize
pagination.value.page = 1
getList()
},
})
onMounted(() => {
getList()
getData()
})
const userInfo = ref({})
const getData = async () => {
const res = await comm.getMerchantInfo()
userInfo.value = res.data.data
}
const getList = async () => {
loading.value = true
try {
const res = await api.getList({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
} catch (error) {
$message.error(error.msg)
}
loading.value = false
}
const ok = async () => {
// if (formData.value.integral < 1000) return $message.warning('提现积分不能小于10000')
const res = await api.apply({
number: formData.value.integral,
})
$message.success(res.msg)
clear()
}
const clear = () => {
formData.value.integral = null
getList()
getData()
}
</script>
<style lang="scss" scoped></style>

15
src/views/sys/api.js Normal file
View File

@@ -0,0 +1,15 @@
import { request } from '@/utils'
export default {
getList: (data) => request.post('/staff/list', data),
// 查找用户
findUser: (data) => request.post('/find/user', data),
// 绑定核销人员
bindUser: (data) => request.post('/staff/user/set', data),
// 获取商家信息
getMerchantInfo: () => request.post('/info'),
// 更新商家信息
updateMerchantInfo: (data) => request.post('/edit', data),
// 取消核销人员
delVerifyUser: (data) => request.post('/staff/user/delete', data),
}

View File

@@ -0,0 +1,237 @@
<script setup>
import api from '../api'
import Upload from '@/components/Upload.vue'
onMounted(() => {
getInfo()
})
const infoData = ref({})
const model = ref({})
const formRef = ref(null)
const showModal = ref(false)
const rules = {
name: {
required: true,
message: '请输入店铺名称',
trigger: 'blur',
},
head_photo: {
required: true,
type: 'array',
message: '请输入上传店铺封面图',
trigger: 'blur',
},
bank: {
required: true,
message: '请输入银行名称',
trigger: 'blur',
},
bank_card: {
required: true,
message: '请输入银行卡号',
trigger: 'blur',
},
bank_name: {
required: true,
message: '请输入账户名称',
trigger: 'blur',
},
bank_user: {
required: true,
message: '请输入法定代表人',
trigger: 'blur',
},
img: {
required: true,
type: 'array',
message: '请输入上传店铺轮播图',
trigger: 'blur',
},
address: {
required: true,
message: '请输入店铺详细地址',
trigger: 'blur',
},
lt: {
required: true,
message: '请查找店铺经纬度',
},
week: {
required: true,
type: 'array',
message: '请选择营业日期',
},
week_start: {
required: true,
message: '请选择营业开始时间',
},
week_end: {
required: true,
message: '请选择营业结束时间',
},
mobile: {
required: true,
message: '请输入店铺手机号',
},
}
const days = ref([
{ label: '周一', value: '1' },
{ label: '周二', value: '2' },
{ label: '周三', value: '3' },
{ label: '周四', value: '4' },
{ label: '周五', value: '5' },
{ label: '周六', value: '6' },
{ label: '周日', value: '7' },
])
const getInfo = async () => {
const res = await api.getMerchantInfo()
infoData.value = res.data.data
console.log((!res.data.data.week_start && 1) || '00:00:00')
model.value = {
...res.data.data,
head_photo: [{ url: res.data.data.head_photo, status: 'finished', id: 1 }],
img: res.data.data.img
.split(',')
.map((item, index) => ({ url: item, status: 'finished', id: index })),
lt: `${res.data.data.lat},${res.data.data.lon}`,
week: res.data.data.week.length === 0 ? [] : res.data.data.week.split(','),
week_start: res.data.data.week_start.length === 0 ? '00:00:00' : res.data.data.week_start,
week_end: res.data.data.week_end.length === 0 ? '00:00:00' : res.data.data.week_end,
}
}
// 选中位置触发该函数
window.addEventListener('message', (res) => {
model.value.lt = `${res.data?.latlng?.lat},${res.data?.latlng?.lng}`
model.value.lat = res.data?.latlng?.lat
model.value.lon = res.data.latlng?.lng
showModal.value = false
})
const submit = () => {
formRef.value?.validate(async (errors) => {
if (!errors) {
const data = {
...model.value,
head_photo: model.value.head_photo[0].url,
img: model.value.img.map((item) => item.url).join(','),
lat: model.value.lat.toString(),
lon: model.value.lon.toString(),
week: model.value.week.join(','),
}
delete data.lt
const res = await api.updateMerchantInfo(data)
$message.success(res.msg)
getInfo()
}
})
}
</script>
<template>
<CommonPage show-footer :title="$route.title">
<n-tabs type="line" animated>
<n-tab-pane name="1" tab="基本信息">
<div>商户名称: {{ infoData.name }}</div>
<div>负责人手机号码: {{ infoData.phone }}</div>
<div>商户类型: {{ infoData.bType === 1 ? '供应商' : '兑换商' }}</div>
<!-- <div>经营类目: {{ infoData.scope }}</div> -->
<div>入驻时间: {{ infoData.add_time }}</div>
</n-tab-pane>
<n-tab-pane name="2" tab="店铺信息">
<!-- {{ model }} -->
<n-form ref="formRef" :model="model" :rules="rules" label-placement="left">
<n-grid :cols="24" :x-gap="24">
<n-form-item-gi :span="24" label="店铺封面图:" path="head_photo">
<Upload v-model:list="model.head_photo" :max="1" />
</n-form-item-gi>
<n-form-item-gi :span="24" label="店铺轮播图:" path="img">
<Upload v-model:list="model.img" :max="5" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="店铺号码:" path="mobile">
<n-input v-model:value="model.mobile" placeholder="请输入店铺号码" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="法定代表人:" path="bank_user">
<n-input v-model:value="model.bank_user" placeholder="请输入法定代表人" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="账户名称:" path="bank_name">
<n-input v-model:value="model.bank_name" placeholder="请输入账户名称" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="开户银行:" path="bank">
<n-input v-model:value="model.bank" placeholder="请输入开户银行" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="银行卡号:" path="bank_card">
<n-input v-model:value="model.bank_card" placeholder="请输入银行卡号" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="营业日期:" path="week">
<n-select v-model:value="model.week" multiple :options="days" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="营业开始时间:" path="week_start">
<n-time-picker
v-model:formatted-value="model.week_start"
value-format="HH:mm:ss"
clearable
/>
</n-form-item-gi>
<n-form-item-gi :span="15" label="营业结束时间:" path="week_end">
<n-time-picker
v-model:formatted-value="model.week_end"
value-format="HH:mm:ss"
clearable
/>
</n-form-item-gi>
<n-form-item-gi :span="15" label="店铺名称:" path="name">
<n-input v-model:value="model.name" placeholder="请输入店铺名称" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="店铺详细地址:" path="address">
<n-input v-model:value="model.address" placeholder="请输入店铺详细地址" />
</n-form-item-gi>
<n-form-item-gi :span="15" label="店铺经纬度:" path="lt">
<n-input-group>
<n-input
v-model:value="model.lt"
placeholder="请查找店铺经纬度"
:style="{ width: '100%' }"
/>
<n-button type="primary" @click="showModal = true">查找位置</n-button>
</n-input-group>
</n-form-item-gi>
<n-form-item-gi :span="15">
<div class="m-auto">
<n-button type="primary" w-100 @click="submit">提交</n-button>
</div>
</n-form-item-gi>
</n-grid>
</n-form>
</n-tab-pane>
<!-- <n-tab-pane name="3" tab="功能信息">开发中</n-tab-pane> -->
</n-tabs>
<!-- h5地图 -->
<n-modal v-if="showModal" v-model:show="showModal">
<n-card
style="width: 600px; height: 600px"
title="查找地图位置"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<iframe
src="https://apis.map.qq.com/tools/locpicker?type=1&key=S3GBZ-WR26O-IXNW2-SXBOD-LZXV6-WAFNO&referer=myapp"
width="100%"
height="100%"
frameborder="0"
></iframe>
</n-card>
</n-modal>
</CommonPage>
</template>
<style scoped lang="scss"></style>

35
src/views/sys/route.js Normal file
View File

@@ -0,0 +1,35 @@
const Layout = () => import('@/layout/index.vue')
export default {
name: '商户设置',
path: '/sys',
component: Layout,
redirect: '/sys_info',
meta: {
title: '商户设置',
icon: 'mdi:account-multiple',
order: 10,
},
children: [
{
name: 'SysInfo',
path: 'sys_info',
component: () => import('./index/index.vue'),
meta: {
title: '商户信息',
icon: 'mdi:account-multiple',
order: 10,
},
},
{
name: 'SysVerify',
path: 'sys_verify',
component: () => import('./verify/index.vue'),
meta: {
title: '核销人员',
icon: 'mdi:account-multiple',
order: 10,
},
},
],
}

View File

@@ -0,0 +1,242 @@
<script setup>
import api from '../api.js'
import { NAvatar, NButton, NPopconfirm } from 'naive-ui'
const loading = ref(false)
const columns = ref([
{
title: '头像',
slot: 'avatarUrl',
align: 'center',
render: (row) => {
return h(NAvatar, { src: row.avatarUrl, round: true, size: 50 })
},
},
{
title: '昵称',
key: 'nickName',
align: 'center',
},
{
title: '手机号',
key: 'phone',
align: 'center',
},
{
title: '操作',
slot: 'action',
align: 'center',
render: (row) => {
console.log(row)
return [
h(
NButton,
{
text: true,
size: 'small',
type: 'primary',
onClick: () => {
addUser(2, row)
},
},
{
default: () => '编辑',
}
),
h(
NPopconfirm,
{
onPositiveClick: () => {
delVerifyUser(row)
},
},
{
default: () => '删除无法撤销,请谨慎!',
trigger: () =>
h(
NButton,
{
text: true,
class: 'ml-10',
size: 'small',
type: 'error',
},
{
default: () => '删除',
}
),
}
),
]
},
},
])
const data = ref([])
const showModal = ref(false)
const showModalTitle = ref('')
const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
onChange: (page) => {
pagination.value.page = page
get_list()
},
})
onMounted(() => {
get_list()
})
const get_list = async () => {
loading.value = true
const res = await api.getList({
PageNum: pagination.value.page,
PageSize: pagination.value.pageSize,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
loading.value = false
}
const addUser = (type, row = {}) => {
showModalTitle.value = type === 1 ? '添加核销人员' : '编辑核销人员'
if (type === 2) {
model.value = {
inputValue: row.phone,
id: row.uid,
url: row.avatarUrl,
name: row.nickName,
}
}
showModal.value = true
}
const formRef = ref(null)
const model = ref({
inputValue: '',
url: '',
id: null,
name: '',
})
const search = async () => {
const res = await api.findUser({
phone: model.value.inputValue,
})
model.value = {
...model.value,
id: res.data.data.uid,
url: res.data.data.avatarUrl,
name: res.data.data.nickName,
}
}
const submit = async () => {
if (!model.value.id) return $message.error('请绑定核销人员')
const res = await api.bindUser({
uid: model.value.id,
})
$message.success(res.msg)
clear()
}
const clear = () => {
model.value = {
inputValue: '',
url: '',
id: null,
name: '',
}
showModal.value = false
get_list()
}
const delVerifyUser = async (row) => {
const res = await api.delVerifyUser({
uid: row.uid,
})
$message.success(res.msg)
get_list()
}
</script>
<template>
<CommonPage show-footer :title="$route.title">
<n-button type="primary" @click="addUser(1)">添加核销人员</n-button>
<n-data-table
class="mt-10"
:loading="loading"
:columns="columns"
:data="data"
:pagination="pagination"
:bordered="false"
remote
/>
<!-- 添加对话框 -->
<n-modal v-model:show="showModal">
<n-card
style="width: 600px"
:title="showModalTitle"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<n-form ref="formRef" :model="model" label-placement="left">
<n-grid :cols="1" :x-gap="24">
<n-form-item-gi class="flex items-center" label="查找用户" :span="20">
<n-input-group>
<n-input
v-model:value="model.inputValue"
placeholder="请输入用户手机号"
:style="{ width: '90%' }"
/>
<n-button type="primary" ghost @click="search">搜索</n-button>
</n-input-group>
</n-form-item-gi>
<n-form-item-gi class="flex items-center" label="绑定用户" :span="12">
<div v-if="model.url !== '' && model.id !== null" class="text-center">
<img class="h-100 w-100 border rounded-5" :src="model.url || ''" />
<div>{{ model.name }}</div>
</div>
<div v-else class="checkBox">+</div>
</n-form-item-gi>
<n-form-item-gi>
<div m-auto>
<n-button type="primary" @click="submit">提交</n-button>
<n-button ml-10 @click="clear">取消</n-button>
</div>
</n-form-item-gi>
</n-grid>
</n-form>
</n-card>
</n-modal>
</CommonPage>
</template>
<style scoped lang="scss">
.checkBox {
width: 100px;
height: 100px;
background-color: rgb(250, 250, 252);
border: 1px dashed rgb(224, 224, 230);
border-radius: 3px;
text-align: center;
line-height: 100px;
font-size: 30px;
&:hover {
cursor: pointer;
border: 1px dashed #18a058;
transition: 1s all;
}
}
</style>

View File

@@ -22,13 +22,18 @@ const columns = ref([
align: 'center',
key: 'ID',
},
{
title: '昵称',
align: 'center',
key: 'nickName',
},
{
title: '头像',
align: 'center',
slot: 'avatar',
slot: 'avatarUrl',
render(row) {
return h('img', {
src: row.avatar,
src: row.avatarUrl,
style: {
width: '30px',
height: '30px',
@@ -52,14 +57,14 @@ const columns = ref([
align: 'center',
key: 'pulse',
},
{
title: '操作',
align: 'center',
slot: 'action',
render(row) {
console.log(row)
},
},
// {
// title: '操作',
// align: 'center',
// slot: 'action',
// render(row) {
// console.log(row)
// },
// },
])
const data = ref([])

View File

@@ -5,6 +5,11 @@ export default {
path: '/user',
component: Layout,
redirect: '/user_list',
meta: {
title: '用户管理',
icon: 'mdi:account-multiple',
order: 10,
},
children: [
{
name: 'Userlist',

View File

@@ -8,51 +8,16 @@
<p text-16>Hello, {{ userStore.name }}</p>
<p mt-5 text-12 op-60>今天又是元气满满的一天</p>
</div>
<!-- <div ml-auto flex items-center>
<n-statistic label="待办" :value="4">
<template #suffix>/ 10</template>
</n-statistic>
<n-statistic label="Stars" ml-80 w-100>
<a href="https://github.com/zclzone/vue-naive-admin">
<img alt="stars" src="https://badgen.net/github/stars/zclzone/vue-naive-admin" />
</a>
</n-statistic>
<n-statistic label="Forks" ml-80 w-100>
<a href="https://github.com/zclzone/vue-naive-admin">
<img alt="forks" src="https://badgen.net/github/forks/zclzone/vue-naive-admin" />
</a>
</n-statistic>
</div> -->
</div>
</n-card>
<!-- <n-card title="项目" size="small" :segmented="true" mt-15 rounded-10>
<template #header-extra>
<n-button text type="primary">更多</n-button>
</template>
<div flex flex-wrap justify-between>
<n-card
v-for="i in 10"
:key="i"
class="mb-10 mt-10 w-300 flex-shrink-0 cursor-pointer"
hover:card-shadow
title="Vue Naive Admin"
size="small"
>
<p op-60>一个基于 Vue3.0ViteNaive UI 的轻量级后台管理模板</p>
</n-card>
<div h-0 w-300></div>
<div h-0 w-300></div>
<div h-0 w-300></div>
<div h-0 w-300></div>
</div>
</n-card> -->
<!-- <Map /> -->
</div>
</AppPage>
</template>
<script setup>
import { useUserStore } from '@/store'
// import Map from '@/components/Map.vue'
const userStore = useUserStore()
</script>

View File

@@ -24,6 +24,7 @@ export default defineConfig(({ command, mode }) => {
plugins: createVitePlugins(viteEnv, isBuild),
server: {
host: '0.0.0.0',
https: false,
port: VITE_PORT,
open: false,
proxy: VITE_USE_PROXY