您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
unique rule的进一步限定与项目分类悬浮状态条效果实现(laravel11的子关联模型调用)
发布时间:2024-11-03 13:01:01编辑:雪饮阅读()
-
unique rule的进一步限定
其实关于unique的进一步限定,我们上次限定了name为unique并且进一步限定了project的id为当前id时则是例外,这种也就是我们进一步限定了。
那么这里我们需要另外完善的是创建项目页面的avatar头像选择后的预览。
然后在Index.vue编写实现方法如
const fileChange=(event)=>{
var file=event.target.files[0];
form.avatar = file;
if(file){
var reader = new FileReader();
reader.onload = (e) => {
form.src=e.target.result;
};
reader.readAsDataURL(file);
}
}
然后对input的事件调用上面的方法
<input type="file" @input="fileChange($event)" />
图片预览
<template v-if="form.src">
<img :src="form.src"/>
</template>
可以看到form加入了src属性,所以form也要定义下
const form = useForm({
name: user.name,
avatar: null,
src:null
});
项目分类悬浮状态条效果实现
这个所谓的悬浮状态条的效果我也不是很清楚,不过我们目前是有项目分类,但是项目创建的时候没有选择分类的地方。
这是要实现的地方。
那么既然要实现项目的分类,则项目表也需要新增一个分类自段
alter table project add column `category` int NOT NULL;
然后我们之前的项目模型对于项目分类暂时我这里认为应该是做一个项目属于一个分类的一对一关系。暂时不考虑一个项目有多个分类的情况。
所以项目模型中需要实现hasOne如
public function category(): HasOne
{
return $this->hasOne(project_category::class,"id","category");
}
那么咱们的项目控制器的update方法中自然也需要将项目分类的前端数据同步创建于数据表中。
/**
* Update the user's project information.
*/
public function update(ProjectUpdateRequest $request):RedirectResponse
{
$this->log(var_export($request->user(),true));
$this->log("uid:".$request->user()->id);
$file=$request->file('avatar');
$path=$file->store('','public');
$url = Storage::url($path);
$uid=$request->user()->id;
$name=$request->input("name");
$category=$request->input("category");
$project=new Project();
$project->uid=$uid;
$project->name=$name;
$project->avatar=$url;
$project->category=$category;
$project->save();
return Redirect::route('project.index');
}
那么在项目列表的回显对应的项目控制器的index方法中也应该将分类回显回去
public function index(Request $request):Response
{
$projects=User::with("projects.category")->find($request->user()->id)->projects;
$categories=project_category::get();
return Inertia::render('Project/Index', [
'projects' => $projects,
'categories'=>$categories
]);
}
可以看到这里用with,这个with不仅能查询关联模型,还能查询关联模型的关联模型,比如这里的projects.category,就表示每个project里面又包含category的hasOne关联。
那么对于前端来说之前的项目详情组件中也应该回显到项目分类。
<div>
<label>project category:</label><span class="text-gray-800">{{ project.category?.name }}</span>
</div>
可以看到这里项目分类名称也是二级json对象的调用,因为关联模型这里是没有直接提到和主模型同一级别,应该是可以的,只是这里暂时不讨论这个了。
那么接下来我们要实现创建项目时候分类的下拉选择。
则index.vue完整如:
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import Project from './Partials/Project.vue';
import InputLabel from '@/Components/InputLabel.vue';
import InputError from '@/Components/InputError.vue';
import TextInput from '@/Components/TextInput.vue';
import {Head, Link, router, useForm, usePage} from '@inertiajs/vue3';
const props=defineProps(['projects','categories']);
console.log("props.projects",props.projects);
const state = useForm({
state:false
});
const mouseoverIndex=useForm({
index:-1
});
const getMouseOverClassByCategory=(index)=>{
var strClass="relative cursor-default select-none py-2 pl-3 pr-9";
if(mouseoverIndex.index==index){
strClass+=" bg-indigo-600 text-white";
}
if(mouseoverIndex.index!=index){
strClass+=" text-gray-900";
}
return strClass;
}
const mouseOverCategory=(index)=>{
mouseoverIndex.index=index;
}
const mouseOutCategory=()=>{
mouseoverIndex.index=-1;
}
const createProject=()=>{
state.state=true;
}
const cancel=()=>{
state.state=false;
}
const user = usePage().props.auth.user;
const form = useForm({
name: user.name,
avatar: null,
src:null,
category:null
});
if(props.categories.length>0){
form.category=props.categories[0].id;
}
function submit() {
router.post(`/project`, {
_method: 'patch',
avatar: form.avatar,
name:form.name,
category:form.category
},{
onSuccess:()=>{
state.state=false;
},
onError:(errors)=>{
console.log("errors",errors);
form.errors.name=errors.name;
}
})
}
const create=()=>{
submit();
}
const fileChange=(event)=>{
var file=event.target.files[0];
form.avatar = file;
if(file){
var reader = new FileReader();
reader.onload = (e) => {
form.src=e.target.result;
};
reader.readAsDataURL(file);
}
}
const getClassBySelectCategory=(index)=>{
var classStr="absolute inset-y-0 right-0 flex items-center pr-4";
if(form.category==props.categories[index].id){
classStr+=" text-indigo-600";
console.log("选中props.categories[index].id:"+props.categories[index].id);
}
if(form.category!=props.categories[index].id){
classStr+=" text-white";
console.log("未选中props.categories[index].id:"+props.categories[index].id);
}
return classStr;
}
const getCategoryIsActivated=(index)=>{
return form.category==props.categories[index].id;
}
const categorySelected=(index)=>{
form.category=props.categories[index].id;
}
const getSelectedCategoryName=()=>{
for(var i in props.categories){
if(props.categories[i].id==form.category){
return props.categories[i].name;
}
}
return "";
}
</script>
<template>
<Head title="Projects" />
<AuthenticatedLayout>
<h2
class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200"
>
Project list (total:{{projects.length}})
</h2>
<div style="display: flex;justify-content: right;">
<PrimaryButton class="ms-4" @click="createProject">Create Project</PrimaryButton>
</div>
<div class="mt-6 bg-white shadow-sm rounded-lg divide-y">
<Project
v-for="project in projects"
:key="project.id"
:project="project"
/>
</div>
</AuthenticatedLayout>
<template v-if="state.state">
<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="relative transform overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all h-96 sm:my-8 sm:w-full sm:max-w-lg">
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<div class="mt-2">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
Project Information
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Update your account's Project information and email address.
</p>
</header>
<form
@submit.prevent="submit"
class="mt-6 space-y-6"
enctype="multipart/form-data"
method="post"
id="projectEdit"
>
<div>
<InputLabel for="name" value="Name" />
<TextInput
id="name"
type="text"
class="mt-1 block w-full"
v-model="form.name"
required
autofocus
autocomplete="name"
/>
<InputError class="mt-2" :message="form.errors.name" />
<input type="file" @input="fileChange($event)" />
<template v-if="form.src">
<img :src="form.src"/>
</template>
<div>
<label id="listbox-label" class="block text-sm/6 font-medium text-gray-900">Category</label>
<div class="relative mt-2">
<button type="button" class="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm/6" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label">
<span class="flex items-center">
<span class="ml-3 block truncate">{{getSelectedCategoryName()}}</span>
</span>
<span class="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2">
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
<path fill-rule="evenodd" d="M10.53 3.47a.75.75 0 0 0-1.06 0L6.22 6.72a.75.75 0 0 0 1.06 1.06L10 5.06l2.72 2.72a.75.75 0 1 0 1.06-1.06l-3.25-3.25Zm-4.31 9.81 3.25 3.25a.75.75 0 0 0 1.06 0l3.25-3.25a.75.75 0 1 0-1.06-1.06L10 14.94l-2.72-2.72a.75.75 0 0 0-1.06 1.06Z" clip-rule="evenodd" />
</svg>
</span>
</button>
<!--
Select popover, show/hide based on select state.
Entering: ""
From: ""
To: ""
Leaving: "transition ease-in duration-100"
From: "opacity-100"
To: "opacity-0"
-->
<ul class="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" tabindex="-1" role="listbox" aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3">
<!--
Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
Highlighted: "bg-indigo-600 text-white", Not Highlighted: "text-gray-900"
-->
<template v-for="(category,index) in categories">
<li :class="getMouseOverClassByCategory(index)" @mouseover="mouseOverCategory(index)" @mouseout="mouseOutCategory" id="listbox-option-0" role="option" @click="categorySelected(index)">
<div class="flex items-center">
<!-- Selected: "font-semibold", Not Selected: "font-normal" -->
<span class="ml-3 block truncate font-normal">{{category.name}}</span>
</div>
<!--
Checkmark, only display for selected option.
Highlighted: "text-white", Not Highlighted: "text-indigo-600"
-->
<span :class="getClassBySelectCategory(index)">
<template v-if="getCategoryIsActivated(index)">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
<path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z" clip-rule="evenodd" />
</svg>
</template>
</span>
</li>
</template>
<!-- More items... -->
</ul>
</div>
</div>
<progress v-if="form.progress" :value="form.progress.percentage" max="100">
{{ form.progress.percentage }}%
</progress>
</div>
</form>
</section>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto" @click="create">Create</button>
<button type="button" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto" @click="cancel">Cancel</button>
</div>
</div>
</div>
</div>
</div>
</template>
</template>
<style>
</style>
这里看起来内容比较多,其实大多数都是来自这里
https://tailwindui.com/components/application-ui/elements/dropdowns
只不过他这个是纯样式,但是里面以注释的形式有交代具体怎样交互。而我这个只不过顺应它的意思进行了具体的实现。由于涉及的几个方法,其实也就一看就懂,并不深奥。只是代码量有点多,所以就一次性贴在这里了。
那么最后的项目创建界面如
需要注意的是这里的高度,默认给的高度很小,我这里修改后的类的class为
relative transform overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all h-screen sm:my-8 sm:w-full sm:max-w-lg"
这里主要注意下overflow-y-auto与h-screen,我这里也注意是调整这两个。
那么关于高度的调整,由于这里是以tailwind为基础css框架的,所以可以参考人家的这个css文档https://www.tailwindcss.cn/docs/height
本期词汇
indigo 靛蓝色;木蓝属植物
convenience 方便,便利
since 自……以后,自……以来
关键字词:laravel,unique
相关文章
- 新建项目的数据验证(基于laravel11+inertia的数据验证
- 新建项目表单的模态框样式(laravel11+inertia+breeze
- laravel中的一对多关系使用与inertia的link使用
- 列出已有项目分类(laravel11+breeze+inertia实现增与
- 图片上传保存逻辑(laravel11+breeze+inertia+vue3实现
- 更改数据结构的两种方法(laravel的migrate)
- mass-assignment批量赋值异常及期间的注意事项(基于la
- blade视图模板的扩展与复用(laravel的inertia)
- 调试请求数据并创建project(为基于sail的laravel项目
- project-create相关的路由及controller定义(laravel-b