一大波更新
feat:添加api接入;更新依赖版本;完善框架;更新样式;
This commit is contained in:
parent
8f695d09a3
commit
d23f5d5668
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
||||||
}
|
|
28
package.json
28
package.json
@ -9,18 +9,24 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ant-design-vue": "4.x",
|
"ant-design-vue": "^4.2.6",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"axios": "^1.7.9",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"pinia": "^2.1.7",
|
"js-md5": "^0.8.3",
|
||||||
"vue": "^3.4.0",
|
"pinia": "^2.3.0",
|
||||||
"vue-router": "4"
|
"postcss": "^8.4.49",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.10.5",
|
"@types/node": "^22.10.2",
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.83.0 ",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^4.3.2",
|
"vite": "^6.0.3",
|
||||||
"vue-tsc": "^1.4.2"
|
"vue-tsc": "^2.1.10"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
||||||
}
|
}
|
||||||
|
8
postcss.config.js
Normal file
8
postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ConfigProvider } from 'ant-design-vue'
|
import { ConfigProvider } from 'ant-design-vue'
|
||||||
import PageLoading from "./components/page-loading/index.vue";
|
import PageLoading from "@/components/page-loading/index.vue";
|
||||||
import { useUserStore } from "./service/user-store.ts";
|
import { useUserStore } from "@/service/user-store.ts";
|
||||||
import Login from "./components/login/index.vue";
|
import Login from "@/components/login/index.vue";
|
||||||
|
|
||||||
|
|
||||||
// 登录相关
|
// 登录相关
|
||||||
|
29
src/assets/libs.scss
Normal file
29
src/assets/libs.scss
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@mixin media-breakpoint-down($name) {
|
||||||
|
@if $name ==sm {
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $name ==md {
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $name ==lg {
|
||||||
|
@media (max-width: 1199px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $name ==xl {
|
||||||
|
@media (max-width: 1399px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ const onInputPaste = (e: ClipboardEvent, pIndex: number, fIndex: number) => {
|
|||||||
<Button type="primary" @click="saveProductValues(productValues)">保存数据</Button>
|
<Button type="primary" @click="saveProductValues(productValues)">保存数据</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="calculator">
|
<div class="calculator">
|
||||||
<Input :rows="4" placeholder="请输入计算公式"/>
|
<Input.TextArea :rows="4" placeholder="请输入计算公式"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {useUserStore} from "../../service/user-store.ts";
|
|
||||||
import {BizError} from "../../core/errors.ts";
|
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {AppConfig} from "../../app-config.ts";
|
|
||||||
|
import {useUserStore} from "@/service/user-store.ts";
|
||||||
|
import {AppConfig} from "@/app-config.ts";
|
||||||
|
import { BizError } from "@/types/core.ts";
|
||||||
|
|
||||||
import leftImage from './login_pic.png'
|
import leftImage from './login_pic.png'
|
||||||
import {Button} from "../button";
|
import {Button} from "../button";
|
||||||
import {LoginModel} from "../../service/api/user";
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
@ -25,7 +26,10 @@ const handleSubmit = () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
store
|
store
|
||||||
.login(params.value)
|
.login(params.value)
|
||||||
.catch((e: BizError) => message.value = e.message)
|
.catch((e: BizError) => {
|
||||||
|
console.log('login error',e)
|
||||||
|
message.value = e.message
|
||||||
|
})
|
||||||
.finally(() => loading.value = false)
|
.finally(() => loading.value = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export class BizError extends Error {
|
|
||||||
code: number;
|
|
||||||
|
|
||||||
constructor(code: number, message: string='') {
|
|
||||||
super(message);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
|
17
src/core/role.ts
Normal file
17
src/core/role.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export enum RoleEnum {
|
||||||
|
// super user
|
||||||
|
ROOT = 'root',
|
||||||
|
// admin user
|
||||||
|
ADMIN = 'admin',
|
||||||
|
// common user
|
||||||
|
USER = 'user',
|
||||||
|
}
|
||||||
|
export const RoleList = [
|
||||||
|
{ label: '超级管理员', value: RoleEnum.ROOT },
|
||||||
|
{ label: '管理员', value: RoleEnum.ADMIN },
|
||||||
|
]
|
||||||
|
export const AllRoleList = [
|
||||||
|
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
...RoleList
|
||||||
|
]
|
5
src/core/string.ts
Normal file
5
src/core/string.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { md5 } from "js-md5";
|
||||||
|
|
||||||
|
export function getMd5(str: string) {
|
||||||
|
return md5(str);
|
||||||
|
}
|
@ -41,7 +41,7 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|||||||
<div class="menu-link" v-for="r in currentMenus">
|
<div class="menu-link" v-for="r in currentMenus">
|
||||||
<router-link class="menu-item" :to="(r.path || '/')">
|
<router-link class="menu-item" :to="(r.path || '/')">
|
||||||
<div class="menu-icon" :class="r.meta?.icon"></div>
|
<div class="menu-icon" :class="r.meta?.icon"></div>
|
||||||
<div>{{ r.meta?.title }}</div>
|
<div class="menu-text">{{ r.meta?.title }}</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,8 +57,8 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|||||||
<MenuItem key="logout">退出登录</MenuItem>
|
<MenuItem key="logout">退出登录</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
<Button>
|
<Button class="flex item-center">
|
||||||
{{ store.userInfo?.nickname }}
|
<span>{{ store.userInfo?.nickname }}</span>
|
||||||
<DownOutlined />
|
<DownOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -138,6 +138,7 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-main-container {
|
.app-main-container {
|
||||||
|
min-width: 1100px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: #f0f2f0;
|
background: #f0f2f0;
|
||||||
|
@ -3,7 +3,7 @@ import { ref, h } from "vue";
|
|||||||
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
Pagination, Select, SelectOption, Input, Button,
|
Pagination, Select, SelectOption, Input, Button,
|
||||||
Space, Table,
|
Space,
|
||||||
} from 'ant-design-vue'
|
} from 'ant-design-vue'
|
||||||
import PageHeader from '../components/page-header.vue'
|
import PageHeader from '../components/page-header.vue'
|
||||||
import { fields, getProductValues } from "../service/data";
|
import { fields, getProductValues } from "../service/data";
|
||||||
@ -54,12 +54,12 @@ const opts = [
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>营养制剂</th>
|
<th>营养制剂</th>
|
||||||
<th v-for="th in columns">{{th.name}}</th>
|
<th v-for="th in columns">{{ th.name }}</th>
|
||||||
<th>操作</th>
|
<th width="150">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(tr,rowIndex) in allDataList" :key="rowIndex">
|
<tr v-for="(tr, rowIndex) in allDataList" :key="rowIndex">
|
||||||
<td>{{ tr.product.name }}</td>
|
<td>{{ tr.product.name }}</td>
|
||||||
<td v-for="it in tr.values">{{ it.value }}</td>
|
<td v-for="it in tr.values">{{ it.value }}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -74,42 +74,9 @@ const opts = [
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-page">
|
<div class="data-page">
|
||||||
<Pagination v-model:current="current" v-model:page-size="pageSize" :total="50"
|
<Pagination v-model:current="current" v-model:page-size="pageSize" :total="50"
|
||||||
:show-total="total => `共 ${total} 条`" />
|
:show-total="total => `共 ${total} 条`" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
.search-form {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 10px 0;
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-form {
|
|
||||||
|
|
||||||
.ant-input,
|
|
||||||
.ant-select .ant-select-selector {
|
|
||||||
background-color: #f7f8fa;
|
|
||||||
border: solid 1px transparent;
|
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--primary-color-1-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-table {}
|
|
||||||
.data-page{
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<p>输出计算</p>
|
<p>输出计算</p>
|
||||||
|
<!-- <DataField /> -->
|
||||||
<Button @click="showMessage">test</Button>
|
<Button @click="showMessage">test</Button>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import DataField from "@/components/data-fields/index.vue"
|
||||||
import Button from "../components/button/button.vue";
|
import Button from "../components/button/button.vue";
|
||||||
import {message} from "../components/message";
|
import {message} from "../components/message";
|
||||||
|
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, h } from "vue";
|
|
||||||
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
|
||||||
import {
|
|
||||||
Pagination, Select, SelectOption, Input, Button,
|
|
||||||
Space, Table,
|
|
||||||
} from 'ant-design-vue'
|
|
||||||
import { fields, getProductValues } from "../service/data";
|
|
||||||
import PageHeader from '../components/page-header.vue'
|
|
||||||
|
|
||||||
const searchParams = ref({
|
|
||||||
type: '',
|
|
||||||
name: ''
|
|
||||||
})
|
|
||||||
const current = ref(2);
|
|
||||||
const pageSize = ref<number>(20);
|
|
||||||
const columns = fields;
|
|
||||||
const allDataList = getProductValues();
|
|
||||||
console.log(allDataList)
|
|
||||||
|
|
||||||
const opts = [
|
|
||||||
{ label: '全部', value: '' },
|
|
||||||
{ label: '能量密度', value: 'power' },
|
|
||||||
{ label: 'Pro(g)', value: 'pro' },
|
|
||||||
{ label: 'Na(g)', value: 'na' },
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="data-fields-container">
|
|
||||||
<PageHeader title="登录账号管理" description="* 超级管理员: 拥有最高权限,可使用全部管理功能。管理员:普通管理权限,不可新建、删除、编辑账号。" />
|
|
||||||
<div class="search-form">
|
|
||||||
<Space :size="20">
|
|
||||||
<Space class="form-item">
|
|
||||||
<span>营养制剂</span>
|
|
||||||
<Input class="search-item" placeholder="请输入营养制剂" v-model:value="searchParams.name" />
|
|
||||||
</Space>
|
|
||||||
<Space class="form-item">
|
|
||||||
<span>类型</span>
|
|
||||||
<Select class="search-item" v-model:value="searchParams.type">
|
|
||||||
<SelectOption v-for="(it, opIndex) in opts" :key="opIndex" :value="it.value">{{ it.label }}
|
|
||||||
</SelectOption>
|
|
||||||
</Select>
|
|
||||||
</Space>
|
|
||||||
<Space class="form-item" :size="15">
|
|
||||||
<Button :icon="h(SearchOutlined)" type="primary">查询</Button>
|
|
||||||
<Button :icon="h(PlusOutlined)" class="btn-info">新增</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
<div class="search-result-table">
|
|
||||||
<div>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>营养制剂</th>
|
|
||||||
<th v-for="th in columns">{{ th.name }}</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(tr, rowIndex) in allDataList" :key="rowIndex">
|
|
||||||
<td>{{ tr.product.name }}</td>
|
|
||||||
<td v-for="it in tr.values">{{ it.value }}</td>
|
|
||||||
<td>
|
|
||||||
<Space :size="20">
|
|
||||||
<a>编辑</a>
|
|
||||||
<a>删除</a>
|
|
||||||
</Space>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="data-page">
|
|
||||||
<Pagination v-model:current="current" v-model:page-size="pageSize" :total="50"
|
|
||||||
:show-total="total => `共 ${total} 条`" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style lang="scss">
|
|
||||||
.search-form {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin: 10px 0;
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-form {
|
|
||||||
|
|
||||||
.ant-input,
|
|
||||||
.ant-select .ant-select-selector {
|
|
||||||
background-color: #f7f8fa;
|
|
||||||
border: solid 1px transparent;
|
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--primary-color-1-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-table {}
|
|
||||||
|
|
||||||
.data-page {
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
132
src/pages/user/index.vue
Normal file
132
src/pages/user/index.vue
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, h } from "vue";
|
||||||
|
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||||
|
import {
|
||||||
|
Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin,
|
||||||
|
Modal,message
|
||||||
|
} from 'ant-design-vue'
|
||||||
|
import { RoleEnum, AllRoleList } from '@/core/role'
|
||||||
|
import { columns, getList,deleteUser } from "@/service/api/user";
|
||||||
|
import PageHeader from '@/components/page-header.vue'
|
||||||
|
import useRequest from "@/service/useRequest";
|
||||||
|
import EditModal from './modal.vue'
|
||||||
|
|
||||||
|
const editUser = ref<UserInfo>()
|
||||||
|
const searchParams = ref<UserSearchParam>({
|
||||||
|
page: 1,
|
||||||
|
limit: 10, role: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const { data: allDataList, loading, run } = useRequest(() => getList(searchParams.value))
|
||||||
|
|
||||||
|
// onMounted(() => {
|
||||||
|
// getList(searchParams.value).then(ret=>{
|
||||||
|
// allDataList.value = ret
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
function search() {
|
||||||
|
searchParams.value = {
|
||||||
|
...searchParams.value,
|
||||||
|
page: 1,
|
||||||
|
limit: 10
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
function deleteUserById(id: number) {
|
||||||
|
deleteUser(id).then(() => {
|
||||||
|
message.success('删除成功')
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(id: number) {
|
||||||
|
Modal.confirm({
|
||||||
|
title:'删除账号',
|
||||||
|
content:'确定要删除该账号吗?',
|
||||||
|
cancelText:'取消',
|
||||||
|
okText:'删除',
|
||||||
|
okType: 'danger',
|
||||||
|
okButtonProps:{
|
||||||
|
|
||||||
|
},
|
||||||
|
onOk:()=>{
|
||||||
|
deleteUserById(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="data-fields-container">
|
||||||
|
<PageHeader title="登录账号管理" description="* 超级管理员: 拥有最高权限,可使用全部管理功能。管理员:普通管理权限,不可新建、删除、编辑账号。" />
|
||||||
|
<div class="search-form">
|
||||||
|
<Space :size="20">
|
||||||
|
<Space class="form-item">
|
||||||
|
<span>姓名</span>
|
||||||
|
<Input class="search-item" placeholder="请输入姓名" v-model:value="searchParams.nickname" />
|
||||||
|
</Space>
|
||||||
|
<Space class="form-item">
|
||||||
|
<span>账号</span>
|
||||||
|
<Input class="search-item" placeholder="请输入账号" v-model:value="searchParams.account" />
|
||||||
|
</Space>
|
||||||
|
<Space class="form-item">
|
||||||
|
<span>类型</span>
|
||||||
|
<Select class="search-item" v-model:value="searchParams.role" style="width: 200px;">
|
||||||
|
<SelectOption v-for="(it, opIndex) in AllRoleList" :key="opIndex" :value="it.value">{{ it.label }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</Space>
|
||||||
|
<Space class="form-item" :size="15">
|
||||||
|
<Button :icon="h(SearchOutlined)" class="flex item-center" type="primary" @click="search()">查询</Button>
|
||||||
|
<Button :icon="h(PlusOutlined)" class="btn-info flex item-center" @click="editUser = {id:0}">新增</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div class="search-result-table">
|
||||||
|
<div>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="th in columns">{{ th.title }}</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(user, rowIndex) in allDataList?.list" :key="rowIndex">
|
||||||
|
<td>{{ user.nickname }}</td>
|
||||||
|
<td>{{ user.account }}</td>
|
||||||
|
<td>{{ user.role == RoleEnum.ROOT ? '超级管理员' : '管理员' }}</td>
|
||||||
|
<td>
|
||||||
|
<Space :size="20">
|
||||||
|
<a @click="editUser = user">编辑</a>
|
||||||
|
<a @click="handleDelete(user.id)">删除</a>
|
||||||
|
</Space>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-if="!allDataList || allDataList?.total == 0" style="padding:30px;">
|
||||||
|
<Empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="allDataList && allDataList?.total > 0" class="data-page">
|
||||||
|
<Pagination v-model:current="searchParams.page" v-model:page-size="searchParams.limit"
|
||||||
|
:total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EditModal v-if="!!editUser" v-model="editUser" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.ant-btn-dangerous{
|
||||||
|
background-color: hsl(359, 100%, 60%);
|
||||||
|
color: white !important;
|
||||||
|
&:hover{
|
||||||
|
background-color: hsl(359, 100%, 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
58
src/pages/user/modal.vue
Normal file
58
src/pages/user/modal.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineModel } from 'vue';
|
||||||
|
import { Modal, Form, FormItem, Input, Select, SelectOption, Space, Button } from 'ant-design-vue';
|
||||||
|
import { RoleList } from '@/core/role';
|
||||||
|
|
||||||
|
const editUser = defineModel<UserInfo>();
|
||||||
|
|
||||||
|
function handleSave(values: UserInfo) {
|
||||||
|
console.log(values)
|
||||||
|
}
|
||||||
|
function handleCancel() {
|
||||||
|
editUser.value = undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal :width="400" :open="true" :footer="null" @cancel="handleCancel">
|
||||||
|
<div class="pt-12">
|
||||||
|
<Form @finish="handleSave" autocomplete="off" v-model="editUser" :label-col="{ span: 5 }"
|
||||||
|
:wrapper-col="{ span: 18 }">
|
||||||
|
<FormItem label="用户名" name="nickname" :rules="[{ required: true, message: '请输入姓名' }]">
|
||||||
|
<Input placeholder="请输入姓名" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="账号" name="account" :rules="[{ required: true, message: '请输入手机号或邮箱' }]">
|
||||||
|
<Input placeholder="请输入手机号或邮箱" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="密码" name="password" :rules="[{
|
||||||
|
validator: async (_:any, value:string) => {
|
||||||
|
console.log('mima',value)
|
||||||
|
if(editUser.value?.id){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!value || value.length < 6 || value.length > 12){
|
||||||
|
throw new Error('密码长度6-12位');
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}]">
|
||||||
|
<Input placeholder="请输入6-12位密码" type="password" />
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem label="角色" name="role" :rules="[{ required: true, message: '请选择用户角色' }]">
|
||||||
|
<Select placeholder="请选择" style="width: 100%;">
|
||||||
|
<SelectOption v-for="(it, opIndex) in RoleList" :key="opIndex" :value="it.value">{{ it.label }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem class="flex justify-end mt-10 mb-0">
|
||||||
|
<Space :size="20">
|
||||||
|
<Button @click="handleCancel">取消</Button>
|
||||||
|
<Button html-type="submit" class="btn-info">确定</Button>
|
||||||
|
</Space>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
@ -43,7 +43,7 @@ export const routes:RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'user',
|
path: 'user',
|
||||||
name: 'user',
|
name: 'user',
|
||||||
component: () => import('./pages/user.vue'),
|
component: () => import('./pages/user/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
role: 'root',
|
role: 'root',
|
||||||
title: '用户管理',
|
title: '用户管理',
|
||||||
|
53
src/service/api/request.ts
Normal file
53
src/service/api/request.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { BizError } from '@/types/core';
|
||||||
|
import {getToken} from '@/service/user-store'
|
||||||
|
|
||||||
|
const axiosService = axios.create({
|
||||||
|
baseURL: '/api/v2',
|
||||||
|
timeout: 5000,
|
||||||
|
headers: {
|
||||||
|
'Authorization': getToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export function request<T>(options: RequestOption) {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const { url, method, data, baseURL, getOriginResult } = options;
|
||||||
|
|
||||||
|
axiosService.request<APIResponse<T>>({
|
||||||
|
url,
|
||||||
|
method: method || 'get',
|
||||||
|
data,
|
||||||
|
baseURL,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.status != 200) {
|
||||||
|
reject(new BizError("Service Internal Exception,Please Try Later!", res.status))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (getOriginResult) {
|
||||||
|
resolve(res.data as unknown as T)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// const
|
||||||
|
const { code, message, data } = res.data
|
||||||
|
if (code == 0) {
|
||||||
|
resolve(data as unknown as T)
|
||||||
|
} else {
|
||||||
|
reject(new BizError(message, code, data))
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
reject(new BizError(e.message, 500))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function post<T>(url:string,data?: any){
|
||||||
|
return request<T>({
|
||||||
|
url,
|
||||||
|
method:'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function get<T>(url:string){
|
||||||
|
return request<T>({url})
|
||||||
|
}
|
@ -1,29 +1,27 @@
|
|||||||
import {BizError} from "../../core/errors.ts";
|
import { sleep } from "@/core/sleep";
|
||||||
|
import { get, post } from "./request";
|
||||||
|
|
||||||
export type LoginModel = {
|
|
||||||
account: string;
|
|
||||||
password: string;
|
export function userLogin(params: LoginModel) {
|
||||||
|
return post<{ token: string; user: UserInfo }>(`/user/login`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fakeUser: UserInfo = {
|
export function getInfo() {
|
||||||
account: "admin",
|
return get<UserInfo>(`/user/info`);
|
||||||
nickname: "管理员",
|
|
||||||
password: "",
|
|
||||||
role: 'root',
|
|
||||||
uid: 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function LoginService(params: LoginModel) {
|
export async function getList(param: UserSearchParam) {
|
||||||
if (params.account == 'admin' && params.password == 'admin') {
|
await sleep(200);
|
||||||
const loginInfo = Date.now().toString(16);
|
return await post<DataList<UserInfo>>(`/user/all`, param);
|
||||||
return {
|
|
||||||
userinfo: fakeUser,
|
|
||||||
token: loginInfo
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new BizError(1001, '用户名或密码错误');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GetLoginInfo() {
|
export function deleteUser(id:number){
|
||||||
return fakeUser;
|
return post(`/user/delete`,{id})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const columns = [
|
||||||
|
{ title: '账号', dataIndex: 'account' },
|
||||||
|
{ title: '姓名', dataIndex: 'nickname' },
|
||||||
|
{ title: '角色', dataIndex: 'role' },
|
||||||
|
]
|
@ -2,177 +2,221 @@ export type BaseModel = {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
}
|
};
|
||||||
export type ProductFieldValue = {
|
export type ProductFieldValue = {
|
||||||
id: number;
|
id: number;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
field_id: number;
|
field_id: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
};
|
||||||
export type ProductValue = {
|
export type ProductValue = {
|
||||||
product: BaseModel;
|
product: BaseModel;
|
||||||
values: ProductFieldValue[]
|
values: ProductFieldValue[];
|
||||||
}
|
};
|
||||||
export const fields: BaseModel[] = [
|
export const fields: BaseModel[] = [
|
||||||
{id: 1, name: '能量密度', alias: 'power'},
|
{ id: 1, name: "能量密度", alias: "power" },
|
||||||
{id: 2, name: '蛋白Pro', alias: 'protein'},
|
{ id: 2, name: "蛋白Pro", alias: "protein" },
|
||||||
{id: 3, name: 'Glu', alias: 'glu'},
|
{ id: 3, name: "Glu", alias: "glu" },
|
||||||
{id: 4, name: 'Fat', alias: 'fat'},
|
{ id: 4, name: "Fat", alias: "fat" },
|
||||||
{id: 5, name: '纤维素', alias: 'cellulose'},
|
{ id: 5, name: "纤维素", alias: "cellulose" },
|
||||||
{id: 6, name: 'Na', alias: 'na'},
|
{ id: 6, name: "Na", alias: "na" },
|
||||||
{id: 7, name: 'K', alias: 'k'},
|
{ id: 7, name: "K", alias: "k" },
|
||||||
{id: 8, name: 'Ca', alias: 'ca'},
|
{ id: 8, name: "Ca", alias: "ca" },
|
||||||
{id: 9, name: 'P', alias: 'p'},
|
{ id: 9, name: "P", alias: "p" },
|
||||||
{id: 10, name: 'Mg', alias: 'mg'},
|
{ id: 10, name: "Mg", alias: "mg" },
|
||||||
]
|
];
|
||||||
export const products: BaseModel[] = [
|
export const products: BaseModel[] = [
|
||||||
{id: 1, name: '爱伦多', alias: 'ailunduo'},
|
{ id: 1, name: "爱伦多", alias: "ailunduo" },
|
||||||
{id: 2, name: '维沃', alias: 'weiwo'},
|
{ id: 2, name: "维沃", alias: "weiwo" },
|
||||||
{id: 3, name: '佳维体', alias: 'jiaweiti'},
|
{ id: 3, name: "佳维体", alias: "jiaweiti" },
|
||||||
{id: 4, name: '伊力佳', alias: 'yilijia'},
|
{ id: 4, name: "伊力佳", alias: "yilijia" },
|
||||||
]
|
];
|
||||||
|
|
||||||
const PRODUCT_VALUES_KEY = 'PRODUCT_VALUES_KEY'
|
const PRODUCT_VALUES_KEY = "PRODUCT_VALUES_KEY";
|
||||||
const product_values_default: ProductFieldValue[] = [
|
const product_values_default: ProductFieldValue[] = [
|
||||||
{
|
{
|
||||||
"id": 1,
|
id: 1,
|
||||||
"product_id": 1,
|
product_id: 1,
|
||||||
"field_id": 1,
|
field_id: 1,
|
||||||
"value": 0,
|
value: 0,
|
||||||
"alias": "ailunduo_power"
|
alias: "ailunduo_power",
|
||||||
}, {"id": 2, "product_id": 1, "field_id": 2, "value": 0, "alias": "ailunduo_protein"}, {
|
},
|
||||||
"id": 3,
|
{ id: 2, product_id: 1, field_id: 2, value: 0, alias: "ailunduo_protein" },
|
||||||
"product_id": 1,
|
{
|
||||||
"field_id": 3,
|
id: 3,
|
||||||
"value": 0,
|
product_id: 1,
|
||||||
"alias": "ailunduo_glu"
|
field_id: 3,
|
||||||
}, {"id": 4, "product_id": 1, "field_id": 4, "value": 0, "alias": "ailunduo_fat"}, {
|
value: 0,
|
||||||
"id": 5,
|
alias: "ailunduo_glu",
|
||||||
"product_id": 1,
|
},
|
||||||
"field_id": 5,
|
{ id: 4, product_id: 1, field_id: 4, value: 0, alias: "ailunduo_fat" },
|
||||||
"value": 0,
|
{
|
||||||
"alias": "ailunduo_cellulose"
|
id: 5,
|
||||||
}, {"id": 6, "product_id": 1, "field_id": 6, "value": 0, "alias": "ailunduo_na"}, {
|
product_id: 1,
|
||||||
"id": 7,
|
field_id: 5,
|
||||||
"product_id": 1,
|
value: 0,
|
||||||
"field_id": 7,
|
alias: "ailunduo_cellulose",
|
||||||
"value": 0,
|
},
|
||||||
"alias": "ailunduo_k"
|
{ id: 6, product_id: 1, field_id: 6, value: 0, alias: "ailunduo_na" },
|
||||||
}, {"id": 8, "product_id": 1, "field_id": 8, "value": 0, "alias": "ailunduo_ca"}, {
|
{
|
||||||
"id": 9,
|
id: 7,
|
||||||
"product_id": 1,
|
product_id: 1,
|
||||||
"field_id": 9,
|
field_id: 7,
|
||||||
"value": 0,
|
value: 0,
|
||||||
"alias": "ailunduo_p"
|
alias: "ailunduo_k",
|
||||||
}, {"id": 10, "product_id": 1, "field_id": 10, "value": 0, "alias": "ailunduo_mg"}, {
|
},
|
||||||
"id": 11,
|
{ id: 8, product_id: 1, field_id: 8, value: 0, alias: "ailunduo_ca" },
|
||||||
"product_id": 2,
|
{
|
||||||
"field_id": 1,
|
id: 9,
|
||||||
"value": 0,
|
product_id: 1,
|
||||||
"alias": "weiwo_power"
|
field_id: 9,
|
||||||
}, {"id": 12, "product_id": 2, "field_id": 2, "value": 0, "alias": "weiwo_protein"}, {
|
value: 0,
|
||||||
"id": 13,
|
alias: "ailunduo_p",
|
||||||
"product_id": 2,
|
},
|
||||||
"field_id": 3,
|
{ id: 10, product_id: 1, field_id: 10, value: 0, alias: "ailunduo_mg" },
|
||||||
"value": 0,
|
{
|
||||||
"alias": "weiwo_glu"
|
id: 11,
|
||||||
}, {"id": 14, "product_id": 2, "field_id": 4, "value": 0, "alias": "weiwo_fat"}, {
|
product_id: 2,
|
||||||
"id": 15,
|
field_id: 1,
|
||||||
"product_id": 2,
|
value: 0,
|
||||||
"field_id": 5,
|
alias: "weiwo_power",
|
||||||
"value": 0,
|
},
|
||||||
"alias": "weiwo_cellulose"
|
{ id: 12, product_id: 2, field_id: 2, value: 0, alias: "weiwo_protein" },
|
||||||
}, {"id": 16, "product_id": 2, "field_id": 6, "value": 0, "alias": "weiwo_na"}, {
|
{
|
||||||
"id": 17,
|
id: 13,
|
||||||
"product_id": 2,
|
product_id: 2,
|
||||||
"field_id": 7,
|
field_id: 3,
|
||||||
"value": 0,
|
value: 0,
|
||||||
"alias": "weiwo_k"
|
alias: "weiwo_glu",
|
||||||
}, {"id": 18, "product_id": 2, "field_id": 8, "value": 0, "alias": "weiwo_ca"}, {
|
},
|
||||||
"id": 19,
|
{ id: 14, product_id: 2, field_id: 4, value: 0, alias: "weiwo_fat" },
|
||||||
"product_id": 2,
|
{
|
||||||
"field_id": 9,
|
id: 15,
|
||||||
"value": 0,
|
product_id: 2,
|
||||||
"alias": "weiwo_p"
|
field_id: 5,
|
||||||
}, {"id": 20, "product_id": 2, "field_id": 10, "value": 0, "alias": "weiwo_mg"}, {
|
value: 0,
|
||||||
"id": 21,
|
alias: "weiwo_cellulose",
|
||||||
"product_id": 3,
|
},
|
||||||
"field_id": 1,
|
{ id: 16, product_id: 2, field_id: 6, value: 0, alias: "weiwo_na" },
|
||||||
"value": 0,
|
{
|
||||||
"alias": "jiaweiti_power"
|
id: 17,
|
||||||
}, {"id": 22, "product_id": 3, "field_id": 2, "value": 0, "alias": "jiaweiti_protein"}, {
|
product_id: 2,
|
||||||
"id": 23,
|
field_id: 7,
|
||||||
"product_id": 3,
|
value: 0,
|
||||||
"field_id": 3,
|
alias: "weiwo_k",
|
||||||
"value": 0,
|
},
|
||||||
"alias": "jiaweiti_glu"
|
{ id: 18, product_id: 2, field_id: 8, value: 0, alias: "weiwo_ca" },
|
||||||
}, {"id": 24, "product_id": 3, "field_id": 4, "value": 0, "alias": "jiaweiti_fat"}, {
|
{
|
||||||
"id": 25,
|
id: 19,
|
||||||
"product_id": 3,
|
product_id: 2,
|
||||||
"field_id": 5,
|
field_id: 9,
|
||||||
"value": 0,
|
value: 0,
|
||||||
"alias": "jiaweiti_cellulose"
|
alias: "weiwo_p",
|
||||||
}, {"id": 26, "product_id": 3, "field_id": 6, "value": 0, "alias": "jiaweiti_na"}, {
|
},
|
||||||
"id": 27,
|
{ id: 20, product_id: 2, field_id: 10, value: 0, alias: "weiwo_mg" },
|
||||||
"product_id": 3,
|
{
|
||||||
"field_id": 7,
|
id: 21,
|
||||||
"value": 0,
|
product_id: 3,
|
||||||
"alias": "jiaweiti_k"
|
field_id: 1,
|
||||||
}, {"id": 28, "product_id": 3, "field_id": 8, "value": 0, "alias": "jiaweiti_ca"}, {
|
value: 0,
|
||||||
"id": 29,
|
alias: "jiaweiti_power",
|
||||||
"product_id": 3,
|
},
|
||||||
"field_id": 9,
|
{ id: 22, product_id: 3, field_id: 2, value: 0, alias: "jiaweiti_protein" },
|
||||||
"value": 0,
|
{
|
||||||
"alias": "jiaweiti_p"
|
id: 23,
|
||||||
}, {"id": 30, "product_id": 3, "field_id": 10, "value": 0, "alias": "jiaweiti_mg"}, {
|
product_id: 3,
|
||||||
"id": 31,
|
field_id: 3,
|
||||||
"product_id": 4,
|
value: 0,
|
||||||
"field_id": 1,
|
alias: "jiaweiti_glu",
|
||||||
"value": 0,
|
},
|
||||||
"alias": "yilijia_power"
|
{ id: 24, product_id: 3, field_id: 4, value: 0, alias: "jiaweiti_fat" },
|
||||||
}, {"id": 32, "product_id": 4, "field_id": 2, "value": 0, "alias": "yilijia_protein"}, {
|
{
|
||||||
"id": 33,
|
id: 25,
|
||||||
"product_id": 4,
|
product_id: 3,
|
||||||
"field_id": 3,
|
field_id: 5,
|
||||||
"value": 0,
|
value: 0,
|
||||||
"alias": "yilijia_glu"
|
alias: "jiaweiti_cellulose",
|
||||||
}, {"id": 34, "product_id": 4, "field_id": 4, "value": 0, "alias": "yilijia_fat"}, {
|
},
|
||||||
"id": 35,
|
{ id: 26, product_id: 3, field_id: 6, value: 0, alias: "jiaweiti_na" },
|
||||||
"product_id": 4,
|
{
|
||||||
"field_id": 5,
|
id: 27,
|
||||||
"value": 0,
|
product_id: 3,
|
||||||
"alias": "yilijia_cellulose"
|
field_id: 7,
|
||||||
}, {"id": 36, "product_id": 4, "field_id": 6, "value": 0, "alias": "yilijia_na"}, {
|
value: 0,
|
||||||
"id": 37,
|
alias: "jiaweiti_k",
|
||||||
"product_id": 4,
|
},
|
||||||
"field_id": 7,
|
{ id: 28, product_id: 3, field_id: 8, value: 0, alias: "jiaweiti_ca" },
|
||||||
"value": 0,
|
{
|
||||||
"alias": "yilijia_k"
|
id: 29,
|
||||||
}, {"id": 38, "product_id": 4, "field_id": 8, "value": 0, "alias": "yilijia_ca"}, {
|
product_id: 3,
|
||||||
"id": 39,
|
field_id: 9,
|
||||||
"product_id": 4,
|
value: 0,
|
||||||
"field_id": 9,
|
alias: "jiaweiti_p",
|
||||||
"value": 0,
|
},
|
||||||
"alias": "yilijia_p"
|
{ id: 30, product_id: 3, field_id: 10, value: 0, alias: "jiaweiti_mg" },
|
||||||
}, {"id": 40, "product_id": 4, "field_id": 10, "value": 0, "alias": "yilijia_mg"}]
|
{
|
||||||
|
id: 31,
|
||||||
|
product_id: 4,
|
||||||
|
field_id: 1,
|
||||||
|
value: 0,
|
||||||
|
alias: "yilijia_power",
|
||||||
|
},
|
||||||
|
{ id: 32, product_id: 4, field_id: 2, value: 0, alias: "yilijia_protein" },
|
||||||
|
{
|
||||||
|
id: 33,
|
||||||
|
product_id: 4,
|
||||||
|
field_id: 3,
|
||||||
|
value: 0,
|
||||||
|
alias: "yilijia_glu",
|
||||||
|
},
|
||||||
|
{ id: 34, product_id: 4, field_id: 4, value: 0, alias: "yilijia_fat" },
|
||||||
|
{
|
||||||
|
id: 35,
|
||||||
|
product_id: 4,
|
||||||
|
field_id: 5,
|
||||||
|
value: 0,
|
||||||
|
alias: "yilijia_cellulose",
|
||||||
|
},
|
||||||
|
{ id: 36, product_id: 4, field_id: 6, value: 0, alias: "yilijia_na" },
|
||||||
|
{
|
||||||
|
id: 37,
|
||||||
|
product_id: 4,
|
||||||
|
field_id: 7,
|
||||||
|
value: 0,
|
||||||
|
alias: "yilijia_k",
|
||||||
|
},
|
||||||
|
{ id: 38, product_id: 4, field_id: 8, value: 0, alias: "yilijia_ca" },
|
||||||
|
{
|
||||||
|
id: 39,
|
||||||
|
product_id: 4,
|
||||||
|
field_id: 9,
|
||||||
|
value: 0,
|
||||||
|
alias: "yilijia_p",
|
||||||
|
},
|
||||||
|
{ id: 40, product_id: 4, field_id: 10, value: 0, alias: "yilijia_mg" },
|
||||||
|
];
|
||||||
|
|
||||||
export function getProductValues() {
|
export function getProductValues() {
|
||||||
const sessionData = localStorage.getItem(PRODUCT_VALUES_KEY)
|
const sessionData = localStorage.getItem(PRODUCT_VALUES_KEY);
|
||||||
const product_values:ProductFieldValue[] =sessionData?JSON.parse(sessionData):product_values_default;
|
const product_values: ProductFieldValue[] = sessionData
|
||||||
const values: Record<number, ProductFieldValue[]> = {}
|
? JSON.parse(sessionData)
|
||||||
product_values.forEach(s => {
|
: product_values_default;
|
||||||
if(!values[s.product_id]) values[s.product_id]= [];
|
const values: Record<number, ProductFieldValue[]> = {};
|
||||||
|
product_values.forEach((s) => {
|
||||||
|
if (!values[s.product_id]) values[s.product_id] = [];
|
||||||
values[s.product_id].push(s);
|
values[s.product_id].push(s);
|
||||||
})
|
});
|
||||||
const results: ProductValue[] = products.map(product=>({product,values: values[product.id]}));
|
const results: ProductValue[] = products.map((product) => ({
|
||||||
|
product,
|
||||||
|
values: values[product.id],
|
||||||
|
}));
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveProductValues(datas: ProductValue[]){
|
export function saveProductValues(datas: ProductValue[]) {
|
||||||
const values = datas.map(s=>s.values).flatMap(s=>s)
|
const values = datas.map((s) => s.values).flatMap((s) => s);
|
||||||
localStorage.setItem(PRODUCT_VALUES_KEY,JSON.stringify(values))
|
localStorage.setItem(PRODUCT_VALUES_KEY, JSON.stringify(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
// const product_values: ProductFieldValue[] = []
|
// const product_values: ProductFieldValue[] = []
|
||||||
@ -189,4 +233,3 @@ export function saveProductValues(datas: ProductValue[]){
|
|||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
// console.log(JSON.stringify(product_values))
|
// console.log(JSON.stringify(product_values))
|
||||||
|
|
||||||
|
47
src/service/useRequest.ts
Normal file
47
src/service/useRequest.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
|
export default function useRequest<T>(
|
||||||
|
service: () => Promise<T>,
|
||||||
|
options?: {
|
||||||
|
manual?: boolean;
|
||||||
|
refreshDeps?: any[];
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const data = ref<T>();
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string>();
|
||||||
|
|
||||||
|
const _request = () => {
|
||||||
|
loading.value = true
|
||||||
|
return service()
|
||||||
|
.then((res) => {
|
||||||
|
data.value = res
|
||||||
|
})
|
||||||
|
.catch((e: Error) => {
|
||||||
|
error.value = e.message
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const refresh = () => {
|
||||||
|
_request()
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
if (!options?.manual) {
|
||||||
|
_request()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (options?.refreshDeps) {
|
||||||
|
watch(options.refreshDeps, () => {
|
||||||
|
_request()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
refresh,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
run: _request
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,20 @@
|
|||||||
import {defineStore} from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import {onMounted, ref} from "vue"
|
import { onMounted, ref } from "vue"
|
||||||
import {GetLoginInfo, LoginService} from "./api/user";
|
import { md5 } from "js-md5";
|
||||||
import {BizError} from "../core/errors.ts";
|
import { sleep } from "@/core/sleep.ts";
|
||||||
import {sleep} from "../core/sleep.ts";
|
import { userLogin, getInfo } from "./api/user";
|
||||||
|
|
||||||
|
import { BizError } from "@/types/core.ts";
|
||||||
|
|
||||||
type LoginParam = {
|
type LoginParam = {
|
||||||
account: string;
|
account: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOGIN_SESSION_KEY = 'x-yy-js-data-user';
|
const LOGIN_SESSION_KEY = 'x-yy-js-data-user';
|
||||||
|
export function getToken() {
|
||||||
|
return localStorage.getItem('x-yy-js-data-user')
|
||||||
|
}
|
||||||
|
|
||||||
export const useUserStore = defineStore('counter', () => {
|
export const useUserStore = defineStore('counter', () => {
|
||||||
const userInfo = ref<UserInfo>()
|
const userInfo = ref<UserInfo>()
|
||||||
@ -17,15 +22,17 @@ export const useUserStore = defineStore('counter', () => {
|
|||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const login = async (data: LoginParam) => {
|
const login = async (data: LoginParam) => {
|
||||||
await sleep(1000);
|
await sleep(500);
|
||||||
const info = await LoginService(data);
|
const info = await userLogin({
|
||||||
userInfo.value = info.userinfo;
|
...data,
|
||||||
|
password: md5(data.password),
|
||||||
|
});
|
||||||
|
userInfo.value = info.user;
|
||||||
localStorage.setItem(LOGIN_SESSION_KEY, info.token)
|
localStorage.setItem(LOGIN_SESSION_KEY, info.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登出
|
// 登出
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
await sleep(1000);
|
|
||||||
localStorage.removeItem(LOGIN_SESSION_KEY)
|
localStorage.removeItem(LOGIN_SESSION_KEY)
|
||||||
userInfo.value = undefined;
|
userInfo.value = undefined;
|
||||||
}
|
}
|
||||||
@ -37,22 +44,22 @@ export const useUserStore = defineStore('counter', () => {
|
|||||||
const getUserInfo = async () => {
|
const getUserInfo = async () => {
|
||||||
const token = localStorage.getItem(LOGIN_SESSION_KEY);
|
const token = localStorage.getItem(LOGIN_SESSION_KEY);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new BizError(401)
|
throw new BizError('need token', 401)
|
||||||
}
|
}
|
||||||
userInfo.value = await GetLoginInfo();
|
userInfo.value = await getInfo();
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
getUserInfo().catch((e: BizError) => {
|
getUserInfo().catch((e: BizError) => {
|
||||||
if (e.code == 401) {
|
if (e.code == 401) {
|
||||||
//router.replace(`/login?redirect=${router.currentRoute.value.path}`).then(() => )
|
//router.replace(`/login?redirect=${router.currentRoute.value.path}`).then(() => )
|
||||||
console.log('401 show login')
|
console.log('401 show login')
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
console.log('onMounted inited')
|
console.log('onMounted inited')
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
userInit.value = true
|
userInit.value = true
|
||||||
},500)
|
}, 500)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@use "./assets/libs" as *;
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont';
|
font-family: 'iconfont';
|
||||||
/* Project id 4404323 */
|
/* Project id 4404323 */
|
||||||
@ -166,6 +168,11 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
|
.anticon {
|
||||||
|
position: relative;
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
&.btn-info {
|
&.btn-info {
|
||||||
background-color: var(--primary-color-2);
|
background-color: var(--primary-color-2);
|
||||||
border-color: var(--primary-color-2);
|
border-color: var(--primary-color-2);
|
||||||
@ -179,31 +186,81 @@ img {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout{
|
||||||
|
.menu{
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-text{
|
||||||
|
display: block;
|
||||||
|
@include media-breakpoint-down(lg) {
|
||||||
|
font-size: 12px;;
|
||||||
|
}
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
|
||||||
|
.ant-input,
|
||||||
|
.ant-select .ant-select-selector {
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color-1-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-table {}
|
||||||
|
|
||||||
|
.data-page {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
th,td{
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 42px;
|
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-bottom: 1px solid #e8e8ec;
|
border-bottom: 1px solid #e8e8ec;
|
||||||
padding: 0 16px;
|
padding: 15px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #f2f3f5;
|
background-color: #f2f3f5;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding:10px 16px;
|
|
||||||
}
|
}
|
||||||
tr{
|
|
||||||
&:hover{
|
tr {
|
||||||
|
&:hover {
|
||||||
background-color: #ebf7ff;
|
background-color: #ebf7ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
src/types/api.d.ts
vendored
Normal file
26
src/types/api.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 请求方式
|
||||||
|
declare type RequestMethod = 'get' | 'post' | 'put' | 'delete'
|
||||||
|
declare type RequestOption = {
|
||||||
|
url: string;
|
||||||
|
method?: RequestMethod;
|
||||||
|
data?: AllType | null;
|
||||||
|
getOriginResult?: boolean;
|
||||||
|
baseURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口返回数据类型
|
||||||
|
declare interface APIResponse<T> {
|
||||||
|
/**
|
||||||
|
* 错误码,0:成功,其他失败
|
||||||
|
*/
|
||||||
|
code: number;
|
||||||
|
/**
|
||||||
|
* 非0情况下,提示信息
|
||||||
|
*/
|
||||||
|
message: string;
|
||||||
|
data?: T;
|
||||||
|
}
|
||||||
|
declare interface DataList<T>{
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
|
}
|
13
src/types/core.ts
Normal file
13
src/types/core.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class BizError extends Error {
|
||||||
|
/**
|
||||||
|
* 错误码
|
||||||
|
*/
|
||||||
|
code = 1;
|
||||||
|
data: any;
|
||||||
|
|
||||||
|
constructor(message: string, code = 1,data?: any) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
23
src/types/user.d.ts
vendored
Normal file
23
src/types/user.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
declare type LoginModel = {
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare type UserSearchParam = {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
nickname?: string;
|
||||||
|
role?: string;
|
||||||
|
account?: string;
|
||||||
|
}
|
||||||
|
declare interface UserInfo {
|
||||||
|
id: number;
|
||||||
|
nickname: string;
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
role: string;
|
||||||
|
created_at: string;
|
||||||
|
last_login: string;
|
||||||
|
updated_at: string;
|
||||||
|
status: number;
|
||||||
|
}
|
13
src/vite-env.d.ts
vendored
13
src/vite-env.d.ts
vendored
@ -2,15 +2,4 @@
|
|||||||
|
|
||||||
type int = number;
|
type int = number;
|
||||||
type double = number;
|
type double = number;
|
||||||
type bool = boolean;
|
type bool = boolean;
|
||||||
type AccountRole = 'root' | 'admin' | 'user';
|
|
||||||
type UserInfo = {
|
|
||||||
uid: number;
|
|
||||||
nickname: string;
|
|
||||||
account: string;
|
|
||||||
password: string;
|
|
||||||
avatar?: string;
|
|
||||||
lastUpdate?:string;
|
|
||||||
createTime?: string;
|
|
||||||
role: AccountRole;
|
|
||||||
}
|
|
56
tailwind.config.js
Normal file
56
tailwind.config.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
|
||||||
|
const themeConfig = {
|
||||||
|
colors: {
|
||||||
|
'primary': '#7356f6',
|
||||||
|
'primary-bg': '#f6f6f6',
|
||||||
|
'active': '#FFE0E0',
|
||||||
|
'primary-red': '#F5222D',
|
||||||
|
'primary-red-70': 'rgba(245,34,45,0.7)',
|
||||||
|
},
|
||||||
|
widths: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./index.html',
|
||||||
|
'./src/**/*.{mjs,js,ts,jsx,tsx,html,vue}'
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
width: {
|
||||||
|
'396px': '396px',
|
||||||
|
'1200px': '1200px',
|
||||||
|
'1000px': '1000px',
|
||||||
|
'chat-input': '800px',
|
||||||
|
...themeConfig.widths,
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
...themeConfig.widths,
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
'basic': '20px',
|
||||||
|
...themeConfig.widths,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
...themeConfig.colors,
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
...themeConfig.colors,
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
...themeConfig.colors,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
screens: {
|
||||||
|
sm: '768px',
|
||||||
|
md: '1024px',
|
||||||
|
lg: '1200px',
|
||||||
|
xl: '1440px',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,11 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
@ -4,31 +4,40 @@ import path from 'path'
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
build: {
|
build: {
|
||||||
// 小于10kb直接base64
|
// 小于10kb直接base64
|
||||||
assetsInlineLimit: 10240,
|
assetsInlineLimit: 10240,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
manualChunks(id) {
|
manualChunks(id) {
|
||||||
// console.log('chunk id',id)
|
// console.log('chunk id',id)
|
||||||
if (id.includes('ant-design')) {
|
if (id.includes('ant-design')) {
|
||||||
return 'ui-libs'
|
return 'ui-libs'
|
||||||
}
|
}
|
||||||
// if (id.includes('vue')) {
|
// if (id.includes('vue')) {
|
||||||
// if (id.includes('node_modules')) {
|
// if (id.includes('node_modules')) {
|
||||||
// return 'vue'
|
// return 'vue'
|
||||||
// }
|
// }
|
||||||
// return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
// return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
base: './',
|
||||||
base: './',
|
resolve: {
|
||||||
// resolve:{
|
alias: {
|
||||||
// alias:{
|
'@': path.resolve(__dirname, './src')
|
||||||
// '@': path.resolve(__dirname,"./src")
|
}
|
||||||
// }
|
},
|
||||||
// },
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3001',
|
||||||
|
changeOrigin: true,
|
||||||
|
// rewrite: path => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user