添加产品(药品)数据(修改数据存储模式);修复useRequest使用
This commit is contained in:
parent
f57daee407
commit
a740a36953
@ -16,7 +16,7 @@ const themeConfig = {
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<PageLoading :class="{ hideLoading: store.userInit }" />
|
<PageLoading :class="{ hideLoading: store.userInit }" />
|
||||||
<Login v-if="store.userInit && (!store.userInfo || store.userInfo.uid < 1)" />
|
<Login v-if="store.userInit && (!store.userInfo || store.userInfo.id < 1)" />
|
||||||
<template v-if="store.userInit">
|
<template v-if="store.userInit">
|
||||||
<ConfigProvider :theme="themeConfig">
|
<ConfigProvider :theme="themeConfig">
|
||||||
<router-view />
|
<router-view />
|
||||||
|
@ -27,3 +27,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-modal {
|
||||||
|
.ant-btn-dangerous {
|
||||||
|
background-color: hsl(359, 100%, 60%);
|
||||||
|
color: white !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: hsl(359, 100%, 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ export enum RoleEnum {
|
|||||||
// common user
|
// common user
|
||||||
USER = 'user',
|
USER = 'user',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RoleList = [
|
export const RoleList = [
|
||||||
{ label: '超级管理员', value: RoleEnum.ROOT },
|
{ label: '超级管理员', value: RoleEnum.ROOT },
|
||||||
{ label: '管理员', value: RoleEnum.ADMIN },
|
{ label: '管理员', value: RoleEnum.ADMIN },
|
||||||
@ -15,3 +16,16 @@ export const AllRoleList = [
|
|||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
...RoleList
|
...RoleList
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export enum ProductCategoryEnum {
|
||||||
|
GUT = 'gut',
|
||||||
|
VEIN = 'vein'
|
||||||
|
}
|
||||||
|
export const ProductCategoryList = [
|
||||||
|
{ label: '肠内制剂', value: ProductCategoryEnum.GUT },
|
||||||
|
{ label: '静脉制剂', value: ProductCategoryEnum.VEIN }
|
||||||
|
]
|
||||||
|
export const AllProductCategoryList = [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
...ProductCategoryList
|
||||||
|
]
|
@ -4,12 +4,17 @@ import { useUserStore } from "../service/user-store.ts";
|
|||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
import { AppConfig } from "../app-config.ts";
|
import { AppConfig } from "../app-config.ts";
|
||||||
import { Dropdown, Menu, MenuItem, Button } from "ant-design-vue";
|
import { Dropdown, Menu, MenuItem, Button, Modal, Form, Input, message } from "ant-design-vue";
|
||||||
import { DownOutlined } from "@ant-design/icons-vue"
|
import { CaretDownOutlined } from "@ant-design/icons-vue"
|
||||||
import { MenuInfo } from "ant-design-vue/es/menu/src/interface";
|
import { MenuInfo } from "ant-design-vue/es/menu/src/interface";
|
||||||
// 获取用户数据
|
// 获取用户数据
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
const showLogo = ref(false)
|
const showLogo = ref(false)
|
||||||
|
const showUpdateModal = ref(false)
|
||||||
|
const modifyLoading = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取当前用户能够访问的菜单
|
// 获取当前用户能够访问的菜单
|
||||||
const currentMenus = computed(() => {
|
const currentMenus = computed(() => {
|
||||||
// 判断是否有登录数据
|
// 判断是否有登录数据
|
||||||
@ -18,14 +23,45 @@ const currentMenus = computed(() => {
|
|||||||
return !s.meta || !s.meta['role'] || store.userInfo?.role == s.meta.role;
|
return !s.meta || !s.meta['role'] || store.userInfo?.role == s.meta.role;
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleMenuClick = ({ key }: MenuInfo) => {
|
const handleMenuClick = ({ key }: MenuInfo) => {
|
||||||
console.log('click menu', key)
|
console.log('click menu', key)
|
||||||
if (key == 'logout') {
|
if (key == 'logout') {
|
||||||
store.logout()
|
store.logout()
|
||||||
} else if (key == 'logout') {
|
} else if (key == 'modifyPassword') {
|
||||||
|
showUpdateModal.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const editData = ref<ModifyUserPassword>({
|
||||||
|
origin_password: '',
|
||||||
|
new_password: '',
|
||||||
|
repeat_password: ''
|
||||||
|
})
|
||||||
|
const handleUpdatePassword = () => {
|
||||||
|
modifyLoading.value = true
|
||||||
|
|
||||||
|
store.updatePassword(editData.value).then(() => {
|
||||||
|
message.success('修改登录密码成功')
|
||||||
|
}).finally(() => {
|
||||||
|
showUpdateModal.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const validateConfirmPass = async (_rule: any, value: string) => {
|
||||||
|
if (value === '') {
|
||||||
|
return Promise.reject('请再次输入新密码确认!');
|
||||||
|
} else if (value !== editData.value.new_password) {
|
||||||
|
return Promise.reject("两次输入的密码不一致");
|
||||||
|
} else if (value === editData.value.origin_password) {
|
||||||
|
return Promise.reject("新密码不能与原始密码相同");
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
function handleHideModal() {
|
||||||
|
// 隐藏
|
||||||
|
showUpdateModal.value = false
|
||||||
|
// 清空数据
|
||||||
|
editData.value = { origin_password: '', new_password: '', repeat_password: '' }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -53,13 +89,13 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|||||||
<Dropdown>
|
<Dropdown>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<Menu @click="handleMenuClick">
|
<Menu @click="handleMenuClick">
|
||||||
<MenuItem key="modifyPassword">修改资料</MenuItem>
|
<MenuItem key="modifyPassword">修改密码</MenuItem>
|
||||||
<MenuItem key="logout">退出登录</MenuItem>
|
<MenuItem key="logout">退出登录</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
<Button class="flex item-center">
|
<Button class="flex items-center">
|
||||||
<span>{{ store.userInfo?.nickname }}</span>
|
<span>{{ store.userInfo?.nickname }}</span>
|
||||||
<DownOutlined />
|
<CaretDownOutlined :style="{ fontSize: '12px' }" />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
@ -68,6 +104,30 @@ const handleMenuClick = ({ key }: MenuInfo) => {
|
|||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal :width="450" :open="showUpdateModal" title="修改登录密码" :mask-closable="false" :destroy-on-close="true"
|
||||||
|
@cancel="handleHideModal" :footer="null">
|
||||||
|
<Form @finish="handleUpdatePassword" autocomplete="off" :model="editData" :label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 16 }">
|
||||||
|
<div class="py-8">
|
||||||
|
<Form.Item label="原始密码" name="origin_password" :rules="[{ required: true, message: '请输入原始密码' }]">
|
||||||
|
<Input type="password" placeholder="请输入原始密码" v-model:value="editData.origin_password" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="新密码" name="new_password"
|
||||||
|
:rules="[{ required: true, message: '请输入新密码' }, { min: 6, max: 12, message: '密码为6-12位长度' }]">
|
||||||
|
<Input type="password" placeholder="请输入新密码" v-model:value="editData.new_password" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="确认密码" name="repeat_password" :rules="[{ validator: validateConfirmPass }]">
|
||||||
|
<Input type="password" placeholder="请输入新密码确认" v-model:value="editData.repeat_password" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<Form.Item class="flex justify-end mb-0">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<Button @click="handleHideModal">取消</Button>
|
||||||
|
<Button type="primary" :loading="modifyLoading" html-type="submit">确定</Button>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,82 +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,
|
|
||||||
} from 'ant-design-vue'
|
|
||||||
import PageHeader from '../components/page-header.vue'
|
|
||||||
import { fields, getProductValues } from "../service/data";
|
|
||||||
|
|
||||||
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 width="150">操作</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"></style>
|
|
133
src/pages/product/edit-modal.vue
Normal file
133
src/pages/product/edit-modal.vue
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineModel, reactive } from 'vue';
|
||||||
|
import { Modal, Form, Input, Space, Button, Select, SelectOption } from 'ant-design-vue';
|
||||||
|
import useRequest from '@/service/useRequest';
|
||||||
|
import { saveProduct } from '@/service/api/product';
|
||||||
|
|
||||||
|
const editData = defineModel<Partial<ProductInfoModel>>();
|
||||||
|
const currentData = reactive<Partial<ProductInfoModel>>({ ...(editData ? editData.value : {}) });
|
||||||
|
|
||||||
|
defineExpose({})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'saved'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { loading, runAsync, error } = useRequest(saveProduct, {
|
||||||
|
manual: true
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
runAsync(currentData).then(() => {
|
||||||
|
editData.value = undefined;
|
||||||
|
emit('saved')
|
||||||
|
})
|
||||||
|
//
|
||||||
|
}
|
||||||
|
function handleCancel() {
|
||||||
|
editData.value = undefined;
|
||||||
|
}
|
||||||
|
function handleFinishError(errorInfo: any) {
|
||||||
|
console.log('handleFinishError', errorInfo)
|
||||||
|
}
|
||||||
|
function onValuesChange() {
|
||||||
|
error.value = undefined
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
{ id: 1, name: "能量密度", alias: "power" },
|
||||||
|
{ id: 2, name: "蛋白Pro", alias: "protein" },
|
||||||
|
{ id: 3, name: "Glu", alias: "glu" },
|
||||||
|
{ id: 4, name: "Fat", alias: "fat" },
|
||||||
|
{ id: 5, name: "纤维素", alias: "cellulose" },
|
||||||
|
{ id: 6, name: "Na", alias: "na" },
|
||||||
|
{ id: 7, name: "K", alias: "k" },
|
||||||
|
{ id: 8, name: "Ca", alias: "ca" },
|
||||||
|
{ id: 9, name: "P", alias: "p" },
|
||||||
|
{ id: 10, name: "Mg", alias: "mg" },
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
// const DataFields = [
|
||||||
|
// { label: '能量密度', name: 'power',message:'请输入能量密度数据' },
|
||||||
|
// { label: '蛋白Pro', name: 'protein',message:'请输入蛋白Pro数据' },
|
||||||
|
// { label: 'Glu', name: 'glu',message:'请输入Glu数据' },
|
||||||
|
// { label: 'Fat', name: 'fat',message:'请输入Fat数据' },
|
||||||
|
// { label: '纤维素', name: 'cellulose',message:'请输入纤维素数据' },
|
||||||
|
// { label: 'Na', name: 'na',message:'请输入Na数据' },
|
||||||
|
// { label: 'K', name: 'k',message:'请输入K数据' },
|
||||||
|
// { label: 'Ca', name: 'ca',message:'请输入Ca数据' },
|
||||||
|
// { label: 'P', name: 'p',message:'请输入P数据' },
|
||||||
|
// { label: 'Mg', name: 'mg',message:'请输入Mg数据' },
|
||||||
|
// ]
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal :width="640" :open="true" :footer="null" @cancel="handleCancel" :mask-closable="false"
|
||||||
|
:destroy-on-close="true">
|
||||||
|
<div class="pt-12">
|
||||||
|
<Form @finish="handleSave" @finishFailed="handleFinishError" @validate="onValuesChange" autocomplete="off"
|
||||||
|
:model="currentData" :label-col="{ span: 7 }" :wrapper-col="{ span: 16 }">
|
||||||
|
<div class="form-item-container grid grid-cols-2">
|
||||||
|
<Form.Item label="营养制剂" name="name" :rules="[{ required: true, message: '请输入营养制剂名称' }]">
|
||||||
|
<Input placeholder="请输入营养制剂名称" v-model:value="currentData.name" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="别名" name="alias" :rules="[{ required: true, message: '请输入制剂别名' }]">
|
||||||
|
<Input placeholder="请输入别名" v-model:value="currentData.alias" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="单位" name="unit" :rules="[{ required: true, message: '请输入制剂单位' }]">
|
||||||
|
<Input placeholder="请输入单位" v-model:value="currentData.unit" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="分类" name="category" :rules="[{ required: true, message: '请输入分类' }]">
|
||||||
|
<Select v-model:value="currentData.category" placeholder="请选择制剂分类">
|
||||||
|
<SelectOption value="gut">肠内制剂</SelectOption>
|
||||||
|
<SelectOption value="vein">静脉制剂</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="能量密度" name="power" :rules="[{ required: true, message: '请输入能量密度' }]">
|
||||||
|
<Input placeholder="请输入能量密度" v-model:value="currentData.power" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="蛋白Pro" name="protein" :rules="[{ required: true, message: '请输入蛋白Pro' }]">
|
||||||
|
<Input placeholder="请输入蛋白Pro" v-model:value="currentData.protein" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Glu" name="glu" :rules="[{ required: true, message: '请输入Glu' }]">
|
||||||
|
<Input placeholder="请输入Glu" v-model:value="currentData.glu" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Fat" name="fat" :rules="[{ required: true, message: '请输入Fat' }]">
|
||||||
|
<Input placeholder="请输入Fat" v-model:value="currentData.fat" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="纤维素" name="cellulose" :rules="[{ required: true, message: '请输入纤维素' }]">
|
||||||
|
<Input placeholder="请输入纤维素" v-model:value="currentData.cellulose" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Na" name="na" :rules="[{ required: true, message: '请输入Na' }]">
|
||||||
|
<Input placeholder="请输入Na" v-model:value="currentData.na" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="K" name="k" :rules="[{ required: true, message: '请输入K' }]">
|
||||||
|
<Input placeholder="请输入K" v-model:value="currentData.k" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Ca" name="ca" :rules="[{ required: true, message: '请输入Ca' }]">
|
||||||
|
<Input placeholder="请输入Ca" v-model:value="currentData.ca" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="P" name="p" :rules="[{ required: true, message: '请输入P' }]">
|
||||||
|
<Input placeholder="请输入P" v-model:value="currentData.p" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Mg" name="mg" :rules="[{ required: true, message: '请输入Mg' }]">
|
||||||
|
<Input placeholder="请输入Mg" v-model:value="currentData.mg" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <Form.Item v-for="(item,index) in DataFields" :key="index"" :label="item.label" :name="item.name" :rules="[{ required: true, message: item.message }]">
|
||||||
|
<Input :placeholder="item.message" v-model:value="currentData.name" />
|
||||||
|
</Form.Item> -->
|
||||||
|
|
||||||
|
<div v-if="error" class="text-red-500 text-center">
|
||||||
|
{{ error?.message }}
|
||||||
|
</div>
|
||||||
|
<Form.Item class="flex justify-end mt-6 mb-0">
|
||||||
|
<Space :size="20">
|
||||||
|
<Button @click="handleCancel">取消</Button>
|
||||||
|
<Button :loading="loading" html-type="submit" class="btn-info">确定</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
112
src/pages/product/index.vue
Normal file
112
src/pages/product/index.vue
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<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 PageHeader from '@/components/page-header.vue'
|
||||||
|
import { AllProductCategoryList } from "@/core/enums";
|
||||||
|
import { ProductCols } from "@/service/api/product";
|
||||||
|
import { getList, deleteProduct } from "@/service/api/product";
|
||||||
|
import useRequest from "@/service/useRequest";
|
||||||
|
import EditModal from "./edit-modal.vue"
|
||||||
|
|
||||||
|
|
||||||
|
const editProduct = ref<Partial<ProductInfoModel>>()
|
||||||
|
const searchParams = ref<ProductSearchParam>({
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
category: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: allDataList, loading, run, refresh } = useRequest(() => getList(searchParams.value))
|
||||||
|
|
||||||
|
function deleteById(id: number) {
|
||||||
|
deleteProduct(id).then(() => {
|
||||||
|
message.success('删除成功')
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(id: number) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '删除营养制剂数据',
|
||||||
|
content:'删除此数据后可能导致系统无法正常计算结果,请确定要删除该数据吗?',
|
||||||
|
cancelText: '取消',
|
||||||
|
okText: '删除',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: () => {
|
||||||
|
deleteById(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.name" />
|
||||||
|
</Space>
|
||||||
|
<Space class="form-item">
|
||||||
|
<span>类型</span>
|
||||||
|
<Select class="search-item w-[120px]" v-model:value="searchParams.category">
|
||||||
|
<SelectOption v-for="(it, opIndex) in AllProductCategoryList" :key="opIndex" :value="it.value">
|
||||||
|
{{ it.label }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</Space>
|
||||||
|
<Space class="form-item" :size="15">
|
||||||
|
<Button :icon="h(SearchOutlined)" type="primary" @click="run">查询</Button>
|
||||||
|
<Button :icon="h(PlusOutlined)" class="btn-info" @click="editProduct = { id: 0 }">新增</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div class="search-result-table">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>营养制剂</th>
|
||||||
|
<th v-for="th in ProductCols">{{ th.name }}</th>
|
||||||
|
<th width="150">计量单位</th>
|
||||||
|
<th width="150">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(tr, rowIndex) in allDataList?.list" :key="rowIndex">
|
||||||
|
<td>{{ tr.name }}</td>
|
||||||
|
<td v-for="th in ProductCols">{{ (tr as any)[th.alias] || 'NULL' }}</td>
|
||||||
|
|
||||||
|
<td>{{ tr.unit }}</td>
|
||||||
|
<td>
|
||||||
|
<Space :size="20">
|
||||||
|
<a @click="editProduct = tr">编辑</a>
|
||||||
|
<a @click="handleDelete(tr.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} 条`" @change="run" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EditModal v-if="!!editProduct" v-model="editProduct" @saved="refresh" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss"></style>
|
@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<p>输出计算</p>
|
|
||||||
<!-- <DataField /> -->
|
|
||||||
<Button @click="showMessage">test</Button>
|
|
||||||
<div>
|
|
||||||
<Demo />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Form @finish="handleSave" @finishFailed="handleFinishError" autocomplete="off" :model="userData"
|
|
||||||
:label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
|
|
||||||
<Form.Item label="用户名" name="nickname" :rules="[{ required: true, message: '请输入姓名' }]">
|
|
||||||
<Input placeholder="请输入姓名" v-model:value="userData.nickname" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="账号" name="account" :rules="[{ required: true, message: '请输入手机号或邮箱' }]">
|
|
||||||
<Input placeholder="请输入手机号或邮箱" v-model:value="userData.account" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="密码" name="password" :rules="passwordRules">
|
|
||||||
<Input placeholder="请输入6-12位密码" type="password" v-model:value="userData.password" />
|
|
||||||
<div>{{ userData?.id ? '如果不需要修改,请不要填写' : '' }}</div>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="角色" name="role" :rules="[{ required: true, message: '请选择用户角色' }]">
|
|
||||||
<Select placeholder="请选择" style="width: 100%;" v-model:value="userData.role">
|
|
||||||
<SelectOption v-for="(it, opIndex) in RoleList" :key="opIndex" :value="it.value">{{ it.label }}
|
|
||||||
</SelectOption>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item class="flex justify-end mt-10 mb-0">
|
|
||||||
<Space :size="20">
|
|
||||||
<Button html-type="submit">确定</Button>
|
|
||||||
</Space>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive, computed } from 'vue'
|
|
||||||
import { Form, Input, Button,Select,SelectOption,Space } from "ant-design-vue"
|
|
||||||
import { RoleList } from '@/core/role';
|
|
||||||
// import DataField from "@/components/data-fields/index.vue"
|
|
||||||
// import Button from "../components/button/button.vue";
|
|
||||||
import { message } from "../components/message";
|
|
||||||
|
|
||||||
import Demo from './result/demo.vue'
|
|
||||||
|
|
||||||
const userData = reactive<Partial<UserInfo>>({});
|
|
||||||
|
|
||||||
|
|
||||||
const passwordRules = computed(() => {
|
|
||||||
return userData?.id ? [
|
|
||||||
{ min: 6, max: 12, message: '请输入6-12位密码' }
|
|
||||||
] : [
|
|
||||||
{ required: true, message: '请输入6-12位密码' },
|
|
||||||
{ min: 6, max: 12, message: '请输入6-12位密码' }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleSave() {
|
|
||||||
console.log(userData)
|
|
||||||
if (userData) {
|
|
||||||
// run(userData)
|
|
||||||
}
|
|
||||||
//
|
|
||||||
}
|
|
||||||
function handleFinishError(errorInfo: any) {
|
|
||||||
console.log('handleFinishError', errorInfo)
|
|
||||||
}
|
|
||||||
const showMessage = () => {
|
|
||||||
message.show(
|
|
||||||
'This notification does not automatically close, and you need to click the close button to close.',
|
|
||||||
'Notification title',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Form v-model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off"
|
|
||||||
@finish="onFinish" @finishFailed="onFinishFailed">
|
|
||||||
<Form.Item label="Username" name="username"
|
|
||||||
:rules="[{ required: true, message: 'Please input your username!' }]">
|
|
||||||
<Input v-model:value="formState.username" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label="Password" name="password"
|
|
||||||
:rules="[{ required: true, message: 'Please input your password!' }]">
|
|
||||||
<InputPassword v-model:value="formState.password" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name="remember" :wrapper-col="{ offset: 8, span: 16 }">
|
|
||||||
<Checkbox v-model:checked="formState.remember">Remember me</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item :wrapper-col="{ offset: 8, span: 16 }">
|
|
||||||
<Button type="primary" html-type="submit">Submit</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
import { Form, Input, Checkbox, Button, InputPassword } from 'ant-design-vue'
|
|
||||||
|
|
||||||
interface FormState {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
remember: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formState = reactive<FormState>({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
remember: true,
|
|
||||||
});
|
|
||||||
const onFinish = (values: any) => {
|
|
||||||
console.log('Success:', values);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFinishFailed = (errorInfo: any) => {
|
|
||||||
console.log('Failed:', errorInfo);
|
|
||||||
};
|
|
||||||
</script>
|
|
18
src/pages/result/index.vue
Normal file
18
src/pages/result/index.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<p>输出计算</p>
|
||||||
|
<!-- <DataField /> -->
|
||||||
|
<Button @click="showMessage">test</Button>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
// import DataField from "@/components/data-fields/index.vue"
|
||||||
|
import Button from "@/components/button/button.vue";
|
||||||
|
import { message } from "@/components/message";
|
||||||
|
|
||||||
|
const showMessage = () => {
|
||||||
|
message.show(
|
||||||
|
'This notification does not automatically close, and you need to click the close button to close.',
|
||||||
|
'Notification title',
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
@ -5,10 +5,12 @@ import {
|
|||||||
Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin,
|
Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin,
|
||||||
Modal, message
|
Modal, message
|
||||||
} from 'ant-design-vue'
|
} from 'ant-design-vue'
|
||||||
import { RoleEnum, AllRoleList } from '@/core/role'
|
|
||||||
|
import { RoleEnum, AllRoleList } from '@/core/enums.ts'
|
||||||
import { columns, getList, deleteUser } from "@/service/api/user";
|
import { columns, getList, deleteUser } from "@/service/api/user";
|
||||||
import PageHeader from '@/components/page-header.vue'
|
import PageHeader from '@/components/page-header.vue'
|
||||||
import useRequest from "@/service/useRequest";
|
import useRequest from "@/service/useRequest";
|
||||||
|
|
||||||
import EditModal from './modal.vue'
|
import EditModal from './modal.vue'
|
||||||
|
|
||||||
const editUser = ref<Partial<UserInfo>>()
|
const editUser = ref<Partial<UserInfo>>()
|
||||||
@ -18,15 +20,10 @@ const searchParams = ref<UserSearchParam>({
|
|||||||
role: ''
|
role: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: allDataList, loading, run, refresh } = useRequest(() => getList(searchParams.value), {
|
const { data: allDataList, loading, run, refresh } = useRequest(() => getList(searchParams.value))
|
||||||
refreshDeps: [searchParams]
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(searchParams, run, { deep: true })
|
|
||||||
function search() {
|
|
||||||
searchParams.value.page = 1;
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
function deleteUserById(id: number) {
|
function deleteUserById(id: number) {
|
||||||
deleteUser(id).then(() => {
|
deleteUser(id).then(() => {
|
||||||
message.success('删除成功')
|
message.success('删除成功')
|
||||||
@ -72,10 +69,8 @@ function handleDelete(id: number) {
|
|||||||
</Select>
|
</Select>
|
||||||
</Space>
|
</Space>
|
||||||
<Space class="form-item" :size="15">
|
<Space class="form-item" :size="15">
|
||||||
<Button :icon="h(SearchOutlined)" type="primary"
|
<Button :icon="h(SearchOutlined)" type="primary" @click="run">查询</Button>
|
||||||
@click="search()">查询</Button>
|
<Button :icon="h(PlusOutlined)" class="btn-info" @click="editUser = { id: 0 }">新增</Button>
|
||||||
<Button :icon="h(PlusOutlined)" class="btn-info"
|
|
||||||
@click="editUser = { id: 0 }">新增</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@ -111,7 +106,7 @@ function handleDelete(id: number) {
|
|||||||
|
|
||||||
<div v-if="allDataList && allDataList?.total > 0" class="data-page">
|
<div v-if="allDataList && allDataList?.total > 0" class="data-page">
|
||||||
<Pagination v-model:current="searchParams.page" v-model:page-size="searchParams.limit"
|
<Pagination v-model:current="searchParams.page" v-model:page-size="searchParams.limit"
|
||||||
:total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" />
|
:total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" @change="run" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -119,12 +114,5 @@ function handleDelete(id: number) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.ant-btn-dangerous {
|
|
||||||
background-color: hsl(359, 100%, 60%);
|
|
||||||
color: white !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: hsl(359, 100%, 70%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,7 +3,7 @@ import { computed, defineModel, reactive } from 'vue';
|
|||||||
import { Modal, Form, Input, Select, SelectOption, Space, Button } from 'ant-design-vue';
|
import { Modal, Form, Input, Select, SelectOption, Space, Button } from 'ant-design-vue';
|
||||||
import { md5 } from 'js-md5';
|
import { md5 } from 'js-md5';
|
||||||
|
|
||||||
import { RoleList } from '@/core/role';
|
import { RoleList } from '@/core/enums';
|
||||||
import useRequest from '@/service/useRequest';
|
import useRequest from '@/service/useRequest';
|
||||||
import { saveUser } from '@/service/api/user';
|
import { saveUser } from '@/service/api/user';
|
||||||
|
|
||||||
|
@ -29,16 +29,16 @@ export const routes:RouteRecordRaw[] = [
|
|||||||
title: '输出计算',
|
title: '输出计算',
|
||||||
icon:'icon-calculator'
|
icon:'icon-calculator'
|
||||||
},
|
},
|
||||||
component: () => import('./pages/result.vue')
|
component: () => import('./pages/result/index.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'data',
|
path: 'product-data',
|
||||||
name: 'data',
|
name: 'product',
|
||||||
meta: {
|
meta: {
|
||||||
title: '数据管理',
|
title: '数据管理',
|
||||||
icon:'icon-input'
|
icon:'icon-input'
|
||||||
},
|
},
|
||||||
component: () => import('./pages/datas.vue')
|
component: () => import('./pages/product/index.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user',
|
path: 'user',
|
||||||
|
28
src/service/api/product.ts
Normal file
28
src/service/api/product.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { sleep } from "@/core/sleep";
|
||||||
|
import { post } from "./request";
|
||||||
|
|
||||||
|
export const ProductCols = [
|
||||||
|
{ id: 1, name: "能量密度", alias: "power" },
|
||||||
|
{ id: 2, name: "蛋白Pro", alias: "protein" },
|
||||||
|
{ id: 3, name: "Glu", alias: "glu" },
|
||||||
|
{ id: 4, name: "Fat", alias: "fat" },
|
||||||
|
{ id: 5, name: "纤维素", alias: "cellulose" },
|
||||||
|
{ id: 6, name: "Na", alias: "na" },
|
||||||
|
{ id: 7, name: "K", alias: "k" },
|
||||||
|
{ id: 8, name: "Ca", alias: "ca" },
|
||||||
|
{ id: 9, name: "P", alias: "p" },
|
||||||
|
{ id: 10, name: "Mg", alias: "mg" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function getList(param: ProductSearchParam) {
|
||||||
|
await sleep(200);
|
||||||
|
return await post<DataList<ProductInfoModel>>(`/product/all`, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteProduct(id: number) {
|
||||||
|
return post(`/product/delete`, { id })
|
||||||
|
}
|
||||||
|
export async function saveProduct(params: Partial<ProductInfoModel>) {
|
||||||
|
await sleep(200);
|
||||||
|
return await post(`/product/save`, params);
|
||||||
|
}
|
@ -6,6 +6,10 @@ export async function saveUser(params: Partial<UserInfo>) {
|
|||||||
await sleep(200);
|
await sleep(200);
|
||||||
return await post<UserInfo>(`/user/save`, params);
|
return await post<UserInfo>(`/user/save`, params);
|
||||||
}
|
}
|
||||||
|
export async function updateUserPassword(params: ModifyUserPassword) {
|
||||||
|
await sleep(200);
|
||||||
|
return await post<UserInfo>(`/user/modify_password`, params);
|
||||||
|
}
|
||||||
|
|
||||||
export function userLogin(params: LoginModel) {
|
export function userLogin(params: LoginModel) {
|
||||||
return post<{ token: string; user: UserInfo }>(`/user/login`, params);
|
return post<{ token: string; user: UserInfo }>(`/user/login`, params);
|
||||||
|
@ -25,6 +25,7 @@ export const fields: BaseModel[] = [
|
|||||||
{ 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" },
|
||||||
|
{ id: 10, name: "计量单位", alias: "unit" },
|
||||||
];
|
];
|
||||||
export const products: BaseModel[] = [
|
export const products: BaseModel[] = [
|
||||||
{ id: 1, name: "爱伦多", alias: "ailunduo" },
|
{ id: 1, name: "爱伦多", alias: "ailunduo" },
|
||||||
@ -211,6 +212,7 @@ export function getProductValues() {
|
|||||||
product,
|
product,
|
||||||
values: values[product.id],
|
values: values[product.id],
|
||||||
}));
|
}));
|
||||||
|
console.log(results);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { defineStore } from "pinia"
|
|||||||
import { onMounted, ref } from "vue"
|
import { onMounted, ref } from "vue"
|
||||||
import { md5 } from "js-md5";
|
import { md5 } from "js-md5";
|
||||||
import { sleep } from "@/core/sleep.ts";
|
import { sleep } from "@/core/sleep.ts";
|
||||||
import { userLogin, getInfo } from "./api/user";
|
import { userLogin, getInfo,saveUser,updateUserPassword } from "./api/user";
|
||||||
|
|
||||||
import { BizError } from "@/types/core.ts";
|
import { BizError } from "@/types/core.ts";
|
||||||
|
|
||||||
@ -37,8 +37,14 @@ export const useUserStore = defineStore('counter', () => {
|
|||||||
userInfo.value = undefined;
|
userInfo.value = undefined;
|
||||||
}
|
}
|
||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
const updateUserInfo = async (info: UserInfo) => {
|
const updateUserInfo = async (info: Partial<UserInfo>) => {
|
||||||
userInfo.value = info
|
await saveUser(info)
|
||||||
|
await getUserInfo()
|
||||||
|
}
|
||||||
|
const updatePassword = async (data: ModifyUserPassword) => {
|
||||||
|
if(!userInfo.value) throw new BizError('please login',401)
|
||||||
|
data.id = userInfo.value.id;
|
||||||
|
await updateUserPassword(data)
|
||||||
}
|
}
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
const getUserInfo = async () => {
|
const getUserInfo = async () => {
|
||||||
@ -69,6 +75,7 @@ export const useUserStore = defineStore('counter', () => {
|
|||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
updateUserInfo,
|
updateUserInfo,
|
||||||
getUserInfo
|
getUserInfo,
|
||||||
|
updatePassword
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
27
src/types/product.d.ts
vendored
Normal file
27
src/types/product.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
declare type ProductSearchParam = {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
name?: string;
|
||||||
|
category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare type ProductInfoModel = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
alias: string;
|
||||||
|
category: string;
|
||||||
|
unit: string;
|
||||||
|
power: number;
|
||||||
|
protein: number;
|
||||||
|
glu: number;
|
||||||
|
fat: number;
|
||||||
|
cellulose: number;
|
||||||
|
na: number;
|
||||||
|
k: number;
|
||||||
|
ca: number;
|
||||||
|
p: number;
|
||||||
|
mg: number;
|
||||||
|
created_at: Date | string;
|
||||||
|
updated_at: Date | string;
|
||||||
|
status: number;
|
||||||
|
}
|
10
src/types/user.d.ts
vendored
10
src/types/user.d.ts
vendored
@ -1,9 +1,9 @@
|
|||||||
declare type LoginModel = {
|
declare interface LoginModel {
|
||||||
account: string;
|
account: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare type UserSearchParam = {
|
declare interface UserSearchParam {
|
||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
@ -21,3 +21,9 @@ declare interface UserInfo {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
status: number;
|
status: number;
|
||||||
}
|
}
|
||||||
|
declare interface ModifyUserPassword {
|
||||||
|
id?: number;
|
||||||
|
origin_password: string;
|
||||||
|
new_password: string;
|
||||||
|
repeat_password: string;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user