Вывалил функционал по добавлению записей в журнал компьютера + несколько микрофиксов и рефакторинг

This commit is contained in:
Dhaverd 2025-03-10 15:55:11 +08:00
parent f64ea3c5e3
commit f48de37c37
13 changed files with 403 additions and 21 deletions

View File

@ -14,15 +14,26 @@ class JobController extends Controller
} }
public function getByComputerId(Request $request){ public function getByComputerId(Request $request){
$request->validate([
'computer_id' => 'required|exists:computers,id'
]);
return Job::select()->where('computer_id', '=', $request->get('computer_id'))->get(); return Job::select()->where('computer_id', '=', $request->get('computer_id'))->get();
} }
public function getById(Request $request){
$request->validate([
'id' => 'required|exists:jobs,id'
]);
return Job::find($request->get('id'));
// return Job::select()->where('id', '=', $request->get('id'))->get();
}
public function create(Request $request) public function create(Request $request)
{ {
$request->validate([ $request->validate([
'computer_id' => 'required|exists:computers,id', 'computer_id' => 'required|exists:computers,id',
'description' => 'required|string|max:256', 'description' => 'required|string|max:256',
'status' => 'nullable|boolean' 'status' => 'required|string|max:256'
]); ]);
$job = Job::create($request->all()); $job = Job::create($request->all());
@ -35,7 +46,7 @@ class JobController extends Controller
'id' => 'required|exists:jobs,id', 'id' => 'required|exists:jobs,id',
'computer_id' => 'required|exists:computers,id', 'computer_id' => 'required|exists:computers,id',
'description' => 'required|string|max:256', 'description' => 'required|string|max:256',
'status' => 'nullable|boolean' 'status' => 'required|string|max:256'
]); ]);
$job = Job::find($request->get('id')); $job = Job::find($request->get('id'));

View File

@ -16,7 +16,7 @@ return new class extends Migration
$table->unsignedBigInteger('computer_id'); $table->unsignedBigInteger('computer_id');
$table->foreign('computer_id')->references('id')->on('computers'); $table->foreign('computer_id')->references('id')->on('computers');
$table->string('description', length: 256); $table->string('description', length: 256);
$table->boolean('status')->default(false); $table->string('status', length: 256);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -89,7 +89,6 @@ export default {
mounted() { mounted() {
this.resizeEventHandler(); this.resizeEventHandler();
window.addEventListener("resize", this.resizeEventHandler, { passive: true }); window.addEventListener("resize", this.resizeEventHandler, { passive: true });
this.userStore.checkUser();
watch(this.userStore, (newStore)=>{ watch(this.userStore, (newStore)=>{
this.authenticated = newStore.user !== null && newStore.user !== undefined; this.authenticated = newStore.user !== null && newStore.user !== undefined;
if (!this.authenticated){ if (!this.authenticated){
@ -98,6 +97,7 @@ export default {
this.$router.push('/'); this.$router.push('/');
} }
}); });
this.userStore.checkUser();
} }
} }
</script> </script>

View File

@ -33,6 +33,7 @@ export const useUserStore = defineStore('user', {
}) })
}, },
async login(email, password, rememberMe) { async login(email, password, rememberMe) {
let responce = false;
await axios.post( await axios.post(
'/api/auth/login', '/api/auth/login',
{ {
@ -42,15 +43,17 @@ export const useUserStore = defineStore('user', {
}).then((res) => { }).then((res) => {
this.setUser(res.data.user); this.setUser(res.data.user);
this.setToken(res.data.accessToken); this.setToken(res.data.accessToken);
return true; responce = true;
}).catch((error) => { }).catch((error) => {
if (!error.response) { if (!error.response) {
return false; responce = false;
} }
return error.response.data.message; responce = error.response.data.message;
}) })
return responce;
}, },
async registration(login, email, password, repeatPassword) { async registration(login, email, password, repeatPassword) {
let responce = false;
await axios.post( await axios.post(
'/api/auth/register', '/api/auth/register',
{ {
@ -61,13 +64,14 @@ export const useUserStore = defineStore('user', {
}).then((res) => { }).then((res) => {
this.setUser(res.data.user); this.setUser(res.data.user);
this.setToken(res.data.accessToken); this.setToken(res.data.accessToken);
return true; responce = true;
}).catch((error) => { }).catch((error) => {
if (!error.response) { if (!error.response) {
return false; responce = false;
} }
return error.response.data.message; responce= error.response.data.message;
}) })
return responce;
}, },
logout() { logout() {
axios.get('/api/auth/logout', axios.get('/api/auth/logout',

View File

@ -12,9 +12,11 @@ export const useComputersStore = defineStore('computers', {
this.token = token; this.token = token;
localStorage.setItem('auth_token', token); localStorage.setItem('auth_token', token);
}, },
checkToken(){ checkToken(){
this.token = useUserStore().token; this.token = useUserStore().token;
}, },
async getById(id){ async getById(id){
if (this.token === null){ if (this.token === null){
this.checkToken(); this.checkToken();
@ -36,6 +38,7 @@ export const useComputersStore = defineStore('computers', {
}); });
return result; return result;
}, },
async getComputerList(user_id){ async getComputerList(user_id){
if (this.token === null){ if (this.token === null){
this.checkToken(); this.checkToken();
@ -55,6 +58,114 @@ export const useComputersStore = defineStore('computers', {
this.computers = response.data; this.computers = response.data;
}) })
}, },
async getJobsByComputerId(computerId){
if (this.token === null){
this.checkToken();
}
let result = null;
await axios.get(
'/api/data/jobs/byComputer',
{
headers: {
Authorization: `Bearer ${this.token}`,
token: this.token
},
params: {
computer_id: computerId
}
}
).then((response)=>{
result = response.data;
});
return result;
},
async getJobsById(id){
if (this.token === null){
this.checkToken();
}
let result = null;
await axios.get(
'/api/data/jobs/byId',
{
headers: {
Authorization: `Bearer ${this.token}`,
token: this.token
},
params: {
id: id
}
}
).then((response)=>{
result = response.data;
});
return result;
},
async createJob(computerId, description, status){
if (this.token === null){
this.checkToken();
}
await axios.post('/api/data/jobs/create', {
computer_id: computerId,
description: description,
status: status
}, {
headers: {
Authorization: `Bearer ${this.token}`,
token: this.token
},
}).then(()=>{
return true;
}).catch(()=>{
return false;
});
},
async updateJob(id, computerId, description, status){
if (this.token === null){
this.checkToken();
}
await axios.post('/api/data/jobs/save', {
id: id,
computer_id: computerId,
description: description,
status: status
}, {
headers: {
Authorization: `Bearer ${this.token}`,
token: this.token
},
}).then(()=>{
this.getComputerList(useUserStore().user['id']);
return true;
}).catch(()=>{
return false;
});
},
async deleteJob(id){
if (this.token === null){
this.checkToken();
}
let result = false;
await axios.post('/api/data/jobs/delete', {
id: id,
}, {
headers: {
Authorization: `Bearer ${this.token}`,
token: this.token
},
}).then(()=>{
this.getComputerList(useUserStore().user['id']);
result = true;
}).catch(()=>{
result = false;
});
return result;
},
async create(name, cpu, ram, motherboard, gpu, additional_info){ async create(name, cpu, ram, motherboard, gpu, additional_info){
if (this.token === null){ if (this.token === null){
this.checkToken(); this.checkToken();
@ -79,6 +190,7 @@ export const useComputersStore = defineStore('computers', {
return false; return false;
}); });
}, },
async update(id, name, cpu, ram, motherboard, gpu, additional_info){ async update(id, name, cpu, ram, motherboard, gpu, additional_info){
if (this.token === null){ if (this.token === null){
this.checkToken(); this.checkToken();
@ -103,6 +215,7 @@ export const useComputersStore = defineStore('computers', {
return false; return false;
}); });
}, },
async delete(id){ async delete(id){
if (this.token === null){ if (this.token === null){
this.checkToken(); this.checkToken();

View File

@ -78,7 +78,7 @@ export default {
<template> <template>
<v-card class="main-bg"> <v-card class="main-bg">
<v-card-title class="d-flex justify-space-between"> <v-card-title class="d-flex justify-space-between">
<span>Создание нового компьютера</span> <span>Изменение компьютера</span>
<v-icon @click="dialogClose" class="cursor-pointer" icon="mdi-close-thick"></v-icon> <v-icon @click="dialogClose" class="cursor-pointer" icon="mdi-close-thick"></v-icon>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>

View File

@ -52,7 +52,7 @@ export default {
} }
}, },
mounted() { mounted() {
this.userStore.checkUser(); this.computerList = this.computersStore.computers;
watch(this.userStore, (newStore)=>{ watch(this.userStore, (newStore)=>{
this.fetching = true; this.fetching = true;
this.authenticated = newStore.user !== null && newStore.user !== undefined; this.authenticated = newStore.user !== null && newStore.user !== undefined;

View File

@ -0,0 +1,62 @@
<script>
import { useComputersStore } from '../../store/computers';
import {rules} from '../../js/rules.js'
export default {
name: "CreateJobForm",
computed: {
rules() {
return rules
}
},
props: {
dialogClose: Function,
computerId: Number
},
data() {
return {
jobDescription: null,
jobStatus: null,
loading: false,
computersStore: useComputersStore()
}
},
methods: {
createJob(){
this.loading = true;
if (!rules.notNull(this.jobDescription) || !rules.notNull(this.jobStatus)){
alert('Описание и статус необходимо заполнить');
this.loading = false;
return;
}
this.computersStore.createJob(
this.computerId,
this.jobDescription,
this.jobStatus
).then((result)=>{
this.loading = false;
this.dialogClose();
});
}
}
}
</script>
<template>
<v-card class="main-bg">
<v-card-title class="d-flex justify-space-between">
<span>Новая запись</span>
<v-icon @click="dialogClose" class="cursor-pointer" icon="mdi-close-thick"></v-icon>
</v-card-title>
<v-card-text>
<v-textarea label="Описание" v-model="jobDescription" placeholder="Замена термопасты 02.03.2025"></v-textarea>
<v-text-field label="Статус" v-model="jobStatus" placeholder="В работе"></v-text-field>
<div class="d-flex justify-center">
<v-btn color="#F0A068FF" :loading="loading" @click="createJob">Создать</v-btn>
</div>
</v-card-text>
</v-card>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,88 @@
<script>
import { useComputersStore } from '../../store/computers.js';
import {rules} from '../../js/rules.js';
import {toRef, ref} from "vue";
export default {
name: "EditJobForm",
computed: {
rules() {
return rules
}
},
props: {
dialogClose: Function,
computerId: Number,
jobId: Number
},
setup(props) {
const id = toRef(props, 'computer_id')
},
mounted() {
this.fetching = true;
this.computersStore.getJobsById(this.jobId).then((responce)=>{
this.jobToEdit = responce;
console.log(this.jobToEdit)
this.jobDescription = this.jobToEdit['description'];
this.jobStatus = this.jobToEdit['status'];
this.fetching = false;
});
},
data() {
return {
jobToEdit: null,
jobDescription: null,
jobStatus: null,
loading: false,
fetching: false,
computersStore: useComputersStore()
}
},
methods: {
editJob(){
this.loading = true;
if (!rules.notNull(this.computerId) || this.computerId === 0 || !rules.notNull(this.jobId) || this.jobId === 0){
alert('Произошла ошибка');
this.loading = false;
return;
}
if (!rules.notNull(this.jobDescription) || !rules.notNull(this.jobStatus)){
alert('Описание и статус необходимо заполнить');
this.loading = false;
return;
}
this.computersStore.updateJob(
this.jobId,
this.computerId,
this.jobDescription,
this.jobStatus
).then((result)=>{
this.loading = false;
this.dialogClose();
});
}
}
}
</script>
<template>
<v-card class="main-bg">
<v-card-title class="d-flex justify-space-between">
<span>Изменение записи</span>
<v-icon @click="dialogClose" class="cursor-pointer" icon="mdi-close-thick"></v-icon>
</v-card-title>
<v-card-text>
<v-skeleton-loader color="grey-darken-4" type="card" v-if="fetching"></v-skeleton-loader>
<div v-if="!fetching">
<v-textarea label="Описание" v-model="jobDescription" placeholder="Замена термопасты 02.03.2025"></v-textarea>
<v-text-field label="Статус" v-model="jobStatus" placeholder="В работе"></v-text-field>
<div class="d-flex justify-center">
<v-btn color="#F0A068FF" :loading="loading" @click="editJob">Сохранить</v-btn>
</div>
</div>
</v-card-text>
</v-card>
</template>
<style scoped>
</style>

View File

@ -1,20 +1,86 @@
<script> <script>
import {useUserStore} from "../store/auth.js"; import {useUserStore} from "../store/auth.js";
import {useComputersStore} from "../store/computers.js"; import {useComputersStore} from "../store/computers.js";
import {ref, watch} from "vue";
import CreateJobForm from "./Jobs/CreateJobForm.vue";
import EditJobForm from "./Jobs/EditJobForm.vue";
export default { export default {
name: "JobsList", name: "JobsList",
components: {EditJobForm, CreateJobForm},
data() { data() {
return { return {
userStore: useUserStore(), userStore: useUserStore(),
computersStore: useComputersStore(), computersStore: useComputersStore(),
computerId: null, computerId: null,
fetching: false, fetching: false,
authenticated: false authenticated: false,
jobs: [],
createDialogShow: false,
editDialogShow: false,
jobToEditId: ref(0),
deleteDialogShow: false,
deleteLoading: false,
jobToDeleteId: ref(0)
}; };
}, },
mounted() { mounted() {
this.computerId = this.$route.query.id; this.computerId = this.$route.query.id;
this.fetching = true;
this.computersStore.getJobsByComputerId(this.computerId).then((response)=>{
this.jobs = response;
this.fetching = false;
});
watch(this.computersStore, (newStore)=>{
this.fetching = true;
newStore.getJobsByComputerId(this.computerId).then((response)=>{
this.jobs = response;
this.fetching = false;
});
})
},
methods: {
updateJobList(){
this.fetching = true;
this.computersStore.getJobsByComputerId(this.computerId).then((response)=>{
this.jobs = response;
this.fetching = false;
});
},
showCreateDialog(){
this.createDialogShow = true;
},
closeCreateDialog(){
this.createDialogShow = false;
this.updateJobList();
},
showEditDialog(id){
this.jobToEditId = id;
this.editDialogShow = true;
},
closeEditDialog(){
this.editDialogShow = false;
this.updateJobList();
},
showDeleteDialog(id){
this.jobToDeleteId = id;
this.deleteDialogShow = true;
},
closeDeleteDialog(){
this.deleteDialogShow = false;
this.updateJobList();
},
deleteComputer(){
this.deleteLoading = true;
this.computersStore.deleteJob(this.jobToDeleteId).then((responce)=>{
this.deleteLoading = false;
if (responce){
this.closeDeleteDialog();
} else {
alert('Произошла ошибка')
}
})
}
} }
} }
</script> </script>
@ -25,12 +91,51 @@ export default {
<v-btn color="#F0A068FF">Назад</v-btn> <v-btn color="#F0A068FF">Назад</v-btn>
</router-link> </router-link>
<v-skeleton-loader v-if="fetching" color="grey-darken-3" class="w-100" type="card"/> <v-skeleton-loader v-if="fetching" color="grey-darken-3" class="w-100" type="card"/>
<div v-if="!fetching"> <div v-if="!fetching" class="pt-3 pb-3">
<div class="d-flex mb-2" v-for="job in jobs">
<v-card class="card-bg text-decoration-none w-100">
<v-card-title>{{ job['description'] }}</v-card-title>
<v-card-text class="d-flex flex-column cursor-pointer">
<label>Статус: {{ job['status'] }}</label>
</v-card-text>
</v-card>
<div @click="showEditDialog(job['id'])" class="card-bg align-self-stretch pa-2 d-flex justify-center align-center rounded-sm cursor-pointer mr-2 ml-2">
<v-icon icon="mdi-pencil"></v-icon>
</div> </div>
<div @click="showDeleteDialog(job['id'])" class="card-bg align-self-stretch pa-2 d-flex justify-center align-center rounded-sm cursor-pointer mr-2 ml-2">
<v-icon icon="mdi-trash-can"></v-icon>
</div>
</div>
<div class="w-100 pa-5" v-if="!fetching">
<v-btn class="w-100" color="#F0A068FF" @click="showCreateDialog" rounded="xs">
<v-icon icon="mdi-plus-thick"></v-icon>
</v-btn>
</div>
</div>
<v-dialog v-model="createDialogShow">
<CreateJobForm :dialogClose="closeCreateDialog" :computerId="computerId"/>
</v-dialog>
<v-dialog v-model="editDialogShow">
<EditJobForm :dialogClose="closeEditDialog" :computerId="computerId" :jobId="jobToEditId"/>
</v-dialog>
<v-dialog v-model="deleteDialogShow" class="w-33">
<v-card class="main-bg">
<v-card-text class="d-flex flex-column">
<v-label class="w-100 text-h5">Вы уверены?</v-label>
<v-label class="w-100">Это действие удалит запись</v-label>
<div class="d-flex w-100 justify-center">
<v-btn color="#F0A068FF" class="ma-2" :loading="deleteLoading" @click="deleteComputer">Да</v-btn>
<v-btn color="#F0A068FF" class="ma-2" @click="closeDeleteDialog">Нет</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.card-bg {
background-color: #272727 !important;
color: white;
}
</style> </style>

View File

@ -14,6 +14,7 @@
@click:append="showPassword = !showPassword" @click:append="showPassword = !showPassword"
required></v-text-field> required></v-text-field>
<v-checkbox v-model="rememberMe" label="Запомнить меня"></v-checkbox> <v-checkbox v-model="rememberMe" label="Запомнить меня"></v-checkbox>
<v-label style="color: red" :style="errorMessageContainerStyle">{{ errorMessage }}</v-label>
<div class="d-flex justify-center" :class="isWide ? '' : 'flex-column align-center'"> <div class="d-flex justify-center" :class="isWide ? '' : 'flex-column align-center'">
<v-btn type="submit" color="#F0A068FF" class="ma-5 flex-grow-0" :class="isWide ? 'w-25' : 'w-100 text-body-1'" :loading="loading">Войти</v-btn> <v-btn type="submit" color="#F0A068FF" class="ma-5 flex-grow-0" :class="isWide ? 'w-25' : 'w-100 text-body-1'" :loading="loading">Войти</v-btn>
<router-link to="/register" class="text-decoration-none link-no-color ma-5" :class="isWide ? 'w-25' : 'w-100'"> <router-link to="/register" class="text-decoration-none link-no-color ma-5" :class="isWide ? 'w-25' : 'w-100'">
@ -53,7 +54,6 @@ export default {
if (isLogged){ if (isLogged){
this.errorMessage = ''; this.errorMessage = '';
this.errorMessageContainerStyle = 'display: none;'; this.errorMessageContainerStyle = 'display: none;';
this.$router.push('/');
} else { } else {
this.errorMessage = 'Authentication error'; this.errorMessage = 'Authentication error';
this.errorMessageContainerStyle = ''; this.errorMessageContainerStyle = '';
@ -63,7 +63,6 @@ export default {
this.errorMessageContainerStyle = ''; this.errorMessageContainerStyle = '';
} }
}); });
this.$router.push('/');
}, },
} }
} }

View File

@ -29,7 +29,6 @@ export default {
if (isRegistred){ if (isRegistred){
this.errorMessage = ''; this.errorMessage = '';
this.errorMessageContainerStyle = 'display: none;'; this.errorMessageContainerStyle = 'display: none;';
this.$router.push('/');
} else { } else {
this.errorMessage = 'Registration error'; this.errorMessage = 'Registration error';
this.errorMessageContainerStyle = ''; this.errorMessageContainerStyle = '';

View File

@ -44,6 +44,7 @@ Route::group(['prefix' => 'data'], function () {
Route::group(['prefix' => 'jobs'], function () { Route::group(['prefix' => 'jobs'], function () {
Route::get('all', [JobController::class, 'index']); Route::get('all', [JobController::class, 'index']);
Route::get('byComputer', [JobController::class, 'getByComputerId']); Route::get('byComputer', [JobController::class, 'getByComputerId']);
Route::get('byId', [JobController::class, 'getById']);
Route::post('create', [JobController::class, 'create']); Route::post('create', [JobController::class, 'create']);
Route::post('save', [JobController::class, 'update']); Route::post('save', [JobController::class, 'update']);
Route::post('delete', [JobController::class, 'destroy']); Route::post('delete', [JobController::class, 'destroy']);